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

saitoha / libsixel / 19987681830

06 Dec 2025 11:13AM UTC coverage: 43.762%. First build
19987681830

push

github

saitoha
threading: rename threading module and merge configuration

10728 of 38707 branches covered (27.72%)

130 of 141 new or added lines in 1 file covered. (92.2%)

14710 of 33614 relevant lines covered (43.76%)

2919171.81 hits per line

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

92.2
/src/threading.c
1
/*
2
 * SPDX-License-Identifier: MIT
3
 *
4
 * Copyright (c) 2025 libsixel developers. See `AUTHORS`.
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a
7
 * copy of this software and associated documentation files
8
 * (the "Software"), to deal in the Software without restriction,
9
 * including without limitation the rights to use, copy, modify, merge,
10
 * publish, distribute, sublicense, and/or sell copies of the Software,
11
 * and to permit persons to whom the
12
 * Software is furnished to do so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included
15
 * in all copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
 * SOFTWARE.
24
 */
25

26
#include "config.h"
27

28
#if defined(__APPLE__) && !defined(_DARWIN_C_SOURCE)
29
/*
30
 * Expose BSD-flavoured typedefs such as u_int from the macOS SDK when the
31
 * build defines _POSIX_C_SOURCE. The platform headers hide these legacy names
32
 * otherwise, and sys/sysctl.h requires them for data structures like
33
 * struct kinfo_proc.
34
 */
35
# define _DARWIN_C_SOURCE
36
#endif
37

38
#include <stdio.h>
39
#include <stdlib.h>
40
#include <string.h>
41

42
#if HAVE_ERRNO_H
43
# include <errno.h>
44
#endif
45
#if HAVE_LIMITS_H
46
# include <limits.h>
47
#endif
48
#if HAVE_UNISTD_H
49
# include <unistd.h>
50
#endif
51
#if HAVE_SYS_TYPES_H
52
# include <sys/types.h>
53
#endif
54
#if HAVE_SYS_SYSCTL_H
55
# include <sys/sysctl.h>
56
#endif
57

58
#include "threading.h"
59

60
/*
61
 * Backend selection is performed here so the header remains lightweight.
62
 * WITH_WINPTHREAD forces the pthread path even on Windows to honor
63
 * user-provided configuration switches.
64
 */
65
#if defined(WITH_WINPTHREAD) && WITH_WINPTHREAD
66
# define SIXEL_USE_PTHREADS 1
67
# define SIXEL_USE_WIN32_THREADS 0
68
#elif defined(_WIN32) && !defined(__CYGWIN__) && !defined(__MSYS__)
69
# define SIXEL_USE_PTHREADS 0
70
# define SIXEL_USE_WIN32_THREADS 1
71
#elif SIXEL_ENABLE_THREADS
72
# define SIXEL_USE_PTHREADS 1
73
# define SIXEL_USE_WIN32_THREADS 0
74
#else
75
# define SIXEL_USE_PTHREADS 0
76
# define SIXEL_USE_WIN32_THREADS 0
77
#endif
78

79
#if SIXEL_USE_WIN32_THREADS
80
# include <windows.h>
81
# include <process.h>
82
#endif
83

84
/*
85
 * Provide a thin portability layer for synchronization primitives so the
86
 * encoder can run on POSIX and Windows platforms without altering the public
87
 * API surface.
88
 */
89

90
#if SIXEL_USE_PTHREADS
91

92
/*
93
 * Abort the process when a pthread call fails in a context where recovery is
94
 * impossible. Encoding without locking guarantees would corrupt state, so we
95
 * surface an explicit diagnostic before terminating.
96
 */
97
static void
98
sixel_pthread_abort(const char *what, int error)
99
{
100
    fprintf(stderr, "libsixel: %s failed: %d\n", what, error);
101
    abort();
102
}
103

104
/*
105
 * Entry point passed to pthread_create. It forwards execution to the user
106
 * supplied callback and stores the integer status for later inspection.
107
 */
108
static void *
109
sixel_thread_trampoline(void *arg)
24✔
110
{
111
    sixel_thread_t *thread;
24✔
112

113
    thread = (sixel_thread_t *)arg;
24✔
114
    thread->result = thread->fn(thread->arg);
24✔
115
    return NULL;
24✔
116
}
8✔
117

118
SIXELAPI int
119
sixel_mutex_init(sixel_mutex_t *mutex)
6✔
120
{
121
    int rc;
6✔
122

123
    if (mutex == NULL) {
6!
124
        return SIXEL_BAD_ARGUMENT;
125
    }
126
    /*
127
     * Default attributes already provide a non-recursive mutex, which is
128
     * sufficient for the encoder's synchronization requirements.
129
     */
130
    rc = pthread_mutex_init(&mutex->native, NULL);
6✔
131
    if (rc != 0) {
6!
132
        errno = rc;
133
        return SIXEL_RUNTIME_ERROR;
134
    }
135
    return SIXEL_OK;
2✔
136
}
2✔
137

138
SIXELAPI void
139
sixel_mutex_destroy(sixel_mutex_t *mutex)
6✔
140
{
141
    int rc;
6✔
142

143
    if (mutex == NULL) {
6!
144
        return;
145
    }
146
    rc = pthread_mutex_destroy(&mutex->native);
6✔
147
    if (rc != 0) {
6!
148
        sixel_pthread_abort("pthread_mutex_destroy", rc);
149
    }
150
}
2!
151

152
SIXELAPI void
153
sixel_mutex_lock(sixel_mutex_t *mutex)
24✔
154
{
155
    int rc;
24✔
156

157
    if (mutex == NULL) {
24!
158
        return;
159
    }
160
    rc = pthread_mutex_lock(&mutex->native);
24✔
161
    if (rc != 0) {
24!
162
        sixel_pthread_abort("pthread_mutex_lock", rc);
163
    }
164
}
8!
165

166
SIXELAPI void
167
sixel_mutex_unlock(sixel_mutex_t *mutex)
24✔
168
{
169
    int rc;
24✔
170

171
    if (mutex == NULL) {
24!
172
        return;
173
    }
174
    rc = pthread_mutex_unlock(&mutex->native);
24✔
175
    if (rc != 0) {
24!
176
        sixel_pthread_abort("pthread_mutex_unlock", rc);
177
    }
178
}
8!
179

180
SIXELAPI int
181
sixel_cond_init(sixel_cond_t *cond)
6✔
182
{
183
    int rc;
6✔
184

185
    if (cond == NULL) {
6!
186
        return SIXEL_BAD_ARGUMENT;
187
    }
188
    /*
189
     * Conditions wake waiters in FIFO order per pthreads documentation. No
190
     * custom attributes are needed for the thread pool queue.
191
     */
192
    rc = pthread_cond_init(&cond->native, NULL);
6✔
193
    if (rc != 0) {
6!
194
        errno = rc;
195
        return SIXEL_RUNTIME_ERROR;
196
    }
197
    return SIXEL_OK;
2✔
198
}
2✔
199

200
SIXELAPI void
201
sixel_cond_destroy(sixel_cond_t *cond)
6✔
202
{
203
    int rc;
6✔
204

205
    if (cond == NULL) {
6!
206
        return;
207
    }
208
    rc = pthread_cond_destroy(&cond->native);
6✔
209
    if (rc != 0) {
6!
210
        sixel_pthread_abort("pthread_cond_destroy", rc);
211
    }
212
}
2!
213

214
SIXELAPI void
215
sixel_cond_wait(sixel_cond_t *cond, sixel_mutex_t *mutex)
1✔
216
{
217
    int rc;
1✔
218

219
    if (cond == NULL || mutex == NULL) {
1!
220
        return;
221
    }
222
    rc = pthread_cond_wait(&cond->native, &mutex->native);
1✔
223
    if (rc != 0) {
1!
224
        sixel_pthread_abort("pthread_cond_wait", rc);
225
    }
226
}
1!
227

228
SIXELAPI void
229
sixel_cond_signal(sixel_cond_t *cond)
230
{
231
    int rc;
232

233
    if (cond == NULL) {
×
234
        return;
235
    }
236
    rc = pthread_cond_signal(&cond->native);
237
    if (rc != 0) {
×
238
        sixel_pthread_abort("pthread_cond_signal", rc);
239
    }
240
}
×
241

242
SIXELAPI void
243
sixel_cond_broadcast(sixel_cond_t *cond)
6✔
244
{
245
    int rc;
6✔
246

247
    if (cond == NULL) {
6!
248
        return;
249
    }
250
    rc = pthread_cond_broadcast(&cond->native);
6✔
251
    if (rc != 0) {
6!
252
        sixel_pthread_abort("pthread_cond_broadcast", rc);
253
    }
254
}
2!
255

256
SIXELAPI int
257
sixel_thread_create(sixel_thread_t *thread, sixel_thread_fn fn, void *arg)
24✔
258
{
259
    int rc;
24✔
260

261
    if (thread == NULL || fn == NULL) {
24!
262
        return SIXEL_BAD_ARGUMENT;
263
    }
264
    /*
265
     * Store context before launching so the trampoline can record the
266
     * callback result inside the same structure without extra allocations.
267
     */
268
    thread->fn = fn;
24✔
269
    thread->arg = arg;
24✔
270
    thread->result = SIXEL_OK;
24✔
271
    thread->started = 0;
24✔
272
    rc = pthread_create(&thread->handle, NULL, sixel_thread_trampoline,
32✔
273
                        thread);
8✔
274
    if (rc != 0) {
24!
275
        errno = rc;
276
        return SIXEL_RUNTIME_ERROR;
277
    }
278
    thread->started = 1;
24✔
279
    return SIXEL_OK;
24✔
280
}
8✔
281

282
SIXELAPI void
283
sixel_thread_join(sixel_thread_t *thread)
24✔
284
{
285
    int rc;
24✔
286

287
    if (thread == NULL || !thread->started) {
24!
288
        return;
289
    }
290
    rc = pthread_join(thread->handle, NULL);
24✔
291
    if (rc != 0) {
24!
292
        sixel_pthread_abort("pthread_join", rc);
293
    }
294
    thread->started = 0;
24✔
295
}
8!
296

297
SIXELAPI int
298
sixel_get_hw_threads(void)
299
{
300
    long count;
301
#if defined(_SC_NPROCESSORS_ONLN)
302
    count = sysconf(_SC_NPROCESSORS_ONLN);
303
    if (count > 0 && count <= (long)INT_MAX) {
×
304
        return (int)count;
305
    }
306
#endif
307
#if defined(__APPLE__)
308
    {
309
        int mib[2];
310
        size_t size;
311
        int value;
312

313
        mib[0] = CTL_HW;
314
        mib[1] = HW_AVAILCPU;
315
        size = sizeof(value);
316
        if (sysctl(mib, 2, &value, &size, NULL, 0) == 0 && value > 0) {
×
317
            return value;
318
        }
319
        mib[1] = HW_NCPU;
320
        size = sizeof(value);
321
        if (sysctl(mib, 2, &value, &size, NULL, 0) == 0 && value > 0) {
×
322
            return value;
323
        }
324
    }
×
325
#endif
326
    return 1;
327
}
328

329
#elif SIXEL_USE_WIN32_THREADS
330

331
/*
332
 * Abort execution on unrecoverable Win32 API failures to mirror pthread path
333
 * semantics. Printing the failing call and error code helps debugging in
334
 * environments where stderr is available.
335
 */
336
static void
337
sixel_win32_abort(const char *what, DWORD error)
338
{
339
    fprintf(stderr, "libsixel: %s failed: %lu\n", what,
340
            (unsigned long)error);
341
    abort();
342
}
343

344
/*
345
 * Trampoline for _beginthreadex. It records the callback result back into the
346
 * owning sixel_thread_t structure so the caller can retrieve it after join.
347
 */
348
static unsigned __stdcall
349
sixel_win32_thread_start(void *arg)
350
{
351
    sixel_thread_t *thread;
352

353
    thread = (sixel_thread_t *)arg;
354
    thread->result = thread->fn(thread->arg);
355
    return 0;
356
}
357

358
SIXELAPI int
359
sixel_mutex_init(sixel_mutex_t *mutex)
360
{
361
    if (mutex == NULL) {
362
        return SIXEL_BAD_ARGUMENT;
363
    }
364
    InitializeCriticalSection(&mutex->native);
365
    return SIXEL_OK;
366
}
367

368
SIXELAPI void
369
sixel_mutex_destroy(sixel_mutex_t *mutex)
370
{
371
    if (mutex == NULL) {
372
        return;
373
    }
374
    DeleteCriticalSection(&mutex->native);
375
}
376

377
SIXELAPI void
378
sixel_mutex_lock(sixel_mutex_t *mutex)
379
{
380
    if (mutex == NULL) {
381
        return;
382
    }
383
    EnterCriticalSection(&mutex->native);
384
}
385

386
SIXELAPI void
387
sixel_mutex_unlock(sixel_mutex_t *mutex)
388
{
389
    if (mutex == NULL) {
390
        return;
391
    }
392
    LeaveCriticalSection(&mutex->native);
393
}
394

395
SIXELAPI int
396
sixel_cond_init(sixel_cond_t *cond)
397
{
398
    if (cond == NULL) {
399
        return SIXEL_BAD_ARGUMENT;
400
    }
401
    InitializeConditionVariable(&cond->native);
402
    return SIXEL_OK;
403
}
404

405
SIXELAPI void
406
sixel_cond_destroy(sixel_cond_t *cond)
407
{
408
    /* CONDITION_VARIABLE does not need explicit teardown. */
409
    (void)cond;
410
}
411

412
SIXELAPI void
413
sixel_cond_wait(sixel_cond_t *cond, sixel_mutex_t *mutex)
414
{
415
    BOOL rc;
416
    DWORD error;
417

418
    if (cond == NULL || mutex == NULL) {
419
        return;
420
    }
421
    rc = SleepConditionVariableCS(&cond->native, &mutex->native, INFINITE);
422
    if (rc == 0) {
423
        error = GetLastError();
424
        sixel_win32_abort("SleepConditionVariableCS", error);
425
    }
426
}
427

428
SIXELAPI void
429
sixel_cond_signal(sixel_cond_t *cond)
430
{
431
    if (cond == NULL) {
432
        return;
433
    }
434
    WakeConditionVariable(&cond->native);
435
}
436

437
SIXELAPI void
438
sixel_cond_broadcast(sixel_cond_t *cond)
439
{
440
    if (cond == NULL) {
441
        return;
442
    }
443
    WakeAllConditionVariable(&cond->native);
444
}
445

446
SIXELAPI int
447
sixel_thread_create(sixel_thread_t *thread, sixel_thread_fn fn, void *arg)
448
{
449
    uintptr_t handle;
450

451
    if (thread == NULL || fn == NULL) {
452
        return SIXEL_BAD_ARGUMENT;
453
    }
454
    thread->fn = fn;
455
    thread->arg = arg;
456
    thread->result = SIXEL_OK;
457
    thread->started = 0;
458
    handle = _beginthreadex(NULL, 0, sixel_win32_thread_start, thread, 0,
459
                            NULL);
460
    if (handle == 0) {
461
        return SIXEL_RUNTIME_ERROR;
462
    }
463
    thread->handle = (HANDLE)handle;
464
    thread->started = 1;
465
    return SIXEL_OK;
466
}
467

468
SIXELAPI void
469
sixel_thread_join(sixel_thread_t *thread)
470
{
471
    DWORD rc;
472
    DWORD error;
473

474
    if (thread == NULL || !thread->started) {
475
        return;
476
    }
477
    rc = WaitForSingleObject(thread->handle, INFINITE);
478
    if (rc != WAIT_OBJECT_0) {
479
        error = (rc == WAIT_FAILED) ? GetLastError() : rc;
480
        sixel_win32_abort("WaitForSingleObject", error);
481
    }
482
    CloseHandle(thread->handle);
483
    thread->handle = NULL;
484
    thread->started = 0;
485
}
486

487
SIXELAPI int
488
sixel_get_hw_threads(void)
489
{
490
    DWORD count;
491
    SYSTEM_INFO info;
492

493
    count = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS);
494
    if (count == 0) {
495
        GetSystemInfo(&info);
496
        count = info.dwNumberOfProcessors;
497
    }
498
    if (count == 0) {
499
        count = 1;
500
    }
501
    return (int)count;
502
}
503

504
#else
505
/*
506
 * Thread support is disabled. Provide stub implementations so callers that
507
 * inadvertently use the API receive a deterministic failure rather than a
508
 * linker error. Mutex and condition helpers become no-ops while creation
509
 * attempts return an explicit runtime error.
510
 */
511
SIXELAPI int
512
sixel_mutex_init(sixel_mutex_t *mutex)
513
{
514
    (void)mutex;
515
    return SIXEL_RUNTIME_ERROR;
516
}
517

518
SIXELAPI void
519
sixel_mutex_destroy(sixel_mutex_t *mutex)
520
{
521
    (void)mutex;
522
}
523

524
SIXELAPI void
525
sixel_mutex_lock(sixel_mutex_t *mutex)
526
{
527
    (void)mutex;
528
}
529

530
SIXELAPI void
531
sixel_mutex_unlock(sixel_mutex_t *mutex)
532
{
533
    (void)mutex;
534
}
535

536
SIXELAPI int
537
sixel_cond_init(sixel_cond_t *cond)
538
{
539
    (void)cond;
540
    return SIXEL_RUNTIME_ERROR;
541
}
542

543
SIXELAPI void
544
sixel_cond_destroy(sixel_cond_t *cond)
545
{
546
    (void)cond;
547
}
548

549
SIXELAPI void
550
sixel_cond_wait(sixel_cond_t *cond, sixel_mutex_t *mutex)
551
{
552
    (void)cond;
553
    (void)mutex;
554
}
555

556
SIXELAPI void
557
sixel_cond_signal(sixel_cond_t *cond)
558
{
559
    (void)cond;
560
}
561

562
SIXELAPI void
563
sixel_cond_broadcast(sixel_cond_t *cond)
564
{
565
    (void)cond;
566
}
567

568
SIXELAPI int
569
sixel_thread_create(sixel_thread_t *thread, sixel_thread_fn fn, void *arg)
570
{
571
    (void)thread;
572
    (void)fn;
573
    (void)arg;
574
    return SIXEL_RUNTIME_ERROR;
575
}
576

577
SIXELAPI void
578
sixel_thread_join(sixel_thread_t *thread)
579
{
580
    (void)thread;
581
}
582

583
SIXELAPI int
584
sixel_get_hw_threads(void)
585
{
586
    return 1;
587
}
588

589
#endif /* SIXEL_USE_PTHREADS */
590

591
/*
592
 * Thread configuration keeps the precedence rules centralized:
593
 *   1. Library callers may override via `sixel_set_threads`.
594
 *   2. Otherwise, `SIXEL_THREADS` from the environment is honored.
595
 *   3. Fallback defaults to single threaded execution.
596
 */
597
typedef struct sixel_thread_config_state {
598
    int requested_threads;
599
    int override_active;
600
    int env_threads;
601
    int env_valid;
602
    int env_checked;
603
} sixel_thread_config_state_t;
604

605
static sixel_thread_config_state_t g_thread_config = {
606
    1,
607
    0,
608
    1,
609
    0,
610
    0
611
};
612

613
static int
614
sixel_threads_token_is_auto(char const *text)
625✔
615
{
616
    if (text == NULL) {
625!
617
        return 0;
618
    }
619

620
    if ((text[0] == 'a' || text[0] == 'A') &&
625!
NEW
621
        (text[1] == 'u' || text[1] == 'U') &&
×
NEW
622
        (text[2] == 't' || text[2] == 'T') &&
×
NEW
623
        (text[3] == 'o' || text[3] == 'O') &&
×
NEW
624
        text[4] == '\0') {
×
NEW
625
        return 1;
×
626
    }
627

628
    return 0;
206✔
629
}
206✔
630

631
SIXELAPI int
632
sixel_threads_normalize(int requested)
485✔
633
{
634
    int normalized;
485✔
635

636
#if SIXEL_ENABLE_THREADS
637
    int hw_threads;
346✔
638

639
    if (requested <= 0) {
346!
640
        hw_threads = sixel_get_hw_threads();
641
        if (hw_threads < 1) {
×
642
            hw_threads = 1;
643
        }
644
        normalized = hw_threads;
645
    } else {
646
        normalized = requested;
206✔
647
    }
648

649
    if (normalized < 1) {
206!
650
        normalized = 1;
651
    }
652
#else
653
    (void)requested;
139✔
654
    normalized = 1;
139✔
655
#endif
656

657
    return normalized;
691✔
658
}
206✔
659

660
static int
661
sixel_threads_parse_env_value(char const *text, int *value)
625✔
662
{
663
    long parsed;
625✔
664
    char *endptr;
625✔
665
    int normalized;
625✔
666

667
    if (text == NULL || value == NULL) {
625!
668
        return 0;
669
    }
670

671
    if (sixel_threads_token_is_auto(text)) {
625!
NEW
672
        normalized = sixel_threads_normalize(0);
×
NEW
673
        *value = normalized;
×
NEW
674
        return 1;
×
675
    }
676

677
    errno = 0;
625✔
678
    parsed = strtol(text, &endptr, 10);
625✔
679
    if (endptr == text || *endptr != '\0' || errno == ERANGE) {
625!
680
        return 0;
681
    }
682

683
    if (parsed < 1) {
625!
684
        normalized = sixel_threads_normalize(1);
685
    } else if (parsed > INT_MAX) {
625!
686
        normalized = sixel_threads_normalize(INT_MAX);
687
    } else {
688
        normalized = sixel_threads_normalize((int)parsed);
625✔
689
    }
690

691
    *value = normalized;
625✔
692
    return 1;
625✔
693
}
206✔
694

695
static void
696
sixel_threads_load_env(void)
2,473✔
697
{
698
    char const *text;
2,473✔
699
    int parsed;
2,473✔
700

701
    if (g_thread_config.env_checked) {
2,473✔
702
        return;
1,848✔
703
    }
704

705
    g_thread_config.env_checked = 1;
625✔
706
    g_thread_config.env_valid = 0;
625✔
707

708
    text = getenv("SIXEL_THREADS");
625✔
709
    if (text == NULL || text[0] == '\0') {
625!
710
        return;
711
    }
712

713
    if (sixel_threads_parse_env_value(text, &parsed)) {
625!
714
        g_thread_config.env_threads = parsed;
625✔
715
        g_thread_config.env_valid = 1;
625✔
716
    }
206✔
717
}
755!
718

719
SIXELAPI int
720
sixel_threads_resolve(void)
2,473✔
721
{
722
    int resolved;
2,473✔
723

724
#if SIXEL_ENABLE_THREADS
725
    if (g_thread_config.override_active) {
1,917!
726
        return g_thread_config.requested_threads;
727
    }
728
#endif
729

730
    sixel_threads_load_env();
2,473✔
731

732
#if SIXEL_ENABLE_THREADS
733
    if (g_thread_config.env_valid) {
1,917!
734
        resolved = g_thread_config.env_threads;
1,917✔
735
    } else {
755✔
736
        resolved = sixel_threads_normalize(0);
737
    }
738
#else
739
    resolved = 1;
556✔
740
#endif
741

742
    return resolved;
1,311✔
743
}
755✔
744

745
/*
746
 * Public setter so CLI/bindings may override the runtime thread preference.
747
 */
748
SIXELAPI void
NEW
749
sixel_set_threads(int threads)
×
750
{
751
#if SIXEL_ENABLE_THREADS
752
    g_thread_config.requested_threads = sixel_threads_normalize(threads);
×
753
#else
754
    (void)threads;
755
    g_thread_config.requested_threads = 1;
756
#endif
NEW
757
    g_thread_config.override_active = 1;
×
NEW
758
}
×
759

760
/* emacs Local Variables:      */
761
/* emacs mode: c               */
762
/* emacs tab-width: 4          */
763
/* emacs indent-tabs-mode: nil */
764
/* emacs c-basic-offset: 4     */
765
/* emacs End:                  */
766
/* vim: set expandtab ts=4 sts=4 sw=4 : */
767
/* EOF */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc