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

moosetechnology / VerveineJ / 25100631120

29 Apr 2026 09:14AM UTC coverage: 52.25% (+0.2%) from 52.087%
25100631120

Pull #224

github

web-flow
Merge 16fd6edcc into 27c4cfb4e
Pull Request #224: Anonymous EnumValues evolution

1983 of 3988 branches covered (49.72%)

Branch coverage included in aggregate %.

209 of 233 new or added lines in 6 files covered. (89.7%)

1 existing line in 1 file now uncovered.

4415 of 8257 relevant lines covered (53.47%)

2.17 hits per line

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

82.19
/app/src/main/java/fr/inria/verveine/extractor/java/visitors/defvisitors/VisitorClassMethodDef.java
1
package fr.inria.verveine.extractor.java.visitors.defvisitors;
2

3
import java.security.MessageDigest;
4
import java.security.NoSuchAlgorithmException;
5
import java.util.ArrayList;
6
import java.util.Collection;
7
import java.util.List;
8

9
import org.apache.commons.codec.digest.DigestUtils;
10
import org.eclipse.jdt.core.dom.*;
11
import org.eclipse.jdt.core.dom.Initializer;
12
import org.eclipse.jdt.core.dom.TypeParameter;
13
import org.moosetechnology.model.famix.famixjavaentities.*;
14
import org.moosetechnology.model.famix.famixjavaentities.Enum;
15
import org.moosetechnology.model.famix.famixtraits.TMethod;
16
import org.moosetechnology.model.famix.famixtraits.TNamedEntity;
17
import org.moosetechnology.model.famix.famixtraits.TWithMethods;
18
import org.moosetechnology.model.famix.famixtraits.TWithTypes;
19

20
import fr.inria.verveine.extractor.java.EntityDictionary;
21
import fr.inria.verveine.extractor.java.VerveineJOptions;
22
import fr.inria.verveine.extractor.java.utils.StubBinding;
23
import fr.inria.verveine.extractor.java.utils.Util;
24
import fr.inria.verveine.extractor.java.visitors.GetVisitedEntityAbstractVisitor;
25

26
/**
27
 * AST Visitor that defines all the (Famix) entities of interest
28
 * Famix entities are stored in a Map along with the IBindings to which they correspond
29
 */
30
public class VisitorClassMethodDef extends GetVisitedEntityAbstractVisitor {
31

32
    protected MessageDigest md5;
33

34
    public VisitorClassMethodDef(EntityDictionary dico, VerveineJOptions options) {
35
        super(dico, options);
4✔
36
        try {
37
            md5 = MessageDigest.getInstance("MD5");
4✔
38
        } catch (NoSuchAlgorithmException e) {
×
39
            e.printStackTrace();
×
40
            md5 = null;
×
41
        }
1✔
42
    }
1✔
43

44
    // VISITOR METHODS
45

46
    @Override
47
    public boolean visit(CompilationUnit node) {
48
        visitCompilationUnit(node);
4✔
49
        return super.visit(node);
4✔
50
    }
51

52
    @Override
53
    public void endVisit(CompilationUnit node) {
54
        endVisitCompilationUnit(node);
3✔
55
    }
1✔
56

57
    /*
58
     * Can only be a class or interface declaration
59
     * Local type: see comment of visit(ClassInstanceCreation node)
60
     */
61
    @Override
62
    public boolean visit(TypeDeclaration node) {
63
        //                System.err.println("TRACE, Visiting TypeDeclaration: "+node.getName().getIdentifier());
64
        ITypeBinding bnd = (ITypeBinding) StubBinding.getDeclarationBinding(node);
4✔
65

66
        @SuppressWarnings("unchecked") List<TypeParameter> typeParameters = (List<TypeParameter>) node.typeParameters();
3✔
67

68
        // may be could use this.referredType instead of dico.ensureFamixClass ?
69
        org.moosetechnology.model.famix.famixtraits.TType fmx;
70
        if (bnd.isInterface()) {
3✔
71
            fmx = dico.ensureFamixInterface(bnd,
7✔
72
                    /*name*/node.getName().getIdentifier(), (TWithTypes)
4✔
73
                            /*owner*/context.top(),
3✔
74
                    /*isGeneric*/typeParameters.size() > 0, node.getModifiers());
7✔
75
        } else if (dico.isThrowable(bnd)) {
5✔
76
            fmx = dico.ensureFamixException(bnd,
7✔
77
                    /*name*/node.getName().getIdentifier(), (TWithTypes)
4✔
78
                            /*owner*/context.top(),
3✔
79
                    /*isGeneric*/typeParameters.size() > 0, node.getModifiers());
5!
80
        } else {
81
            fmx = dico.ensureFamixClass(bnd,
6✔
82
                    /*name*/node.getName().getIdentifier(),
4✔
83
                    /*owner*/context.top(),
2✔
84
                    /*isGeneric*/typeParameters.size() > 0, node.getModifiers());
7✔
85
        }
86
        if (fmx != null) {
2!
87
            Util.recursivelySetIsStub(fmx, false);
3✔
88

89
            // if it is a generic and some parameterizedTypes were created for it,
90
            // they are marked as stub which is not right
91
            if (typeParameters.size() > 0) {
3✔
92
                for (ParametricClass candidate : dico.getEntityByName(ParametricClass.class, node.getName().getIdentifier())) {
16✔
93
                    candidate.setIsStub(false);
4✔
94
                }
1✔
95
            }
96

97
            this.context.pushType(fmx);
4✔
98

99
            if (options.withAnchors()) {
4!
100
                dico.addSourceAnchor(fmx, node);
6✔
101
            }
102

103

104
            return super.visit(node);
4✔
105
        } else {
NEW
106
            return false;
×
107
        }
108
    }
109

110
    @Override
111
    public void endVisit(TypeDeclaration node) {
112
        this.context.popType();
4✔
113
        super.endVisit(node);
3✔
114
    }
1✔
115

116
    /**
117
     * See field {@link VisitorClassMethodDef#anonymousSuperTypeName}<br>
118
     * We could test if it is a local type (inner/anonymous) and not define it in case it does not make any reference
119
     * to anything outside its owner class. But it would be a lot of work for probably little gain.
120
     */
121
    @Override
122
    public boolean visit(ClassInstanceCreation node) {
123
        //                System.err.println("TRACE, Visiting ClassInstanceCreation: " + node);
124
        possiblyAnonymousClassDeclaration(node);
3✔
125
        return super.visit(node);
4✔
126
    }
127

128
    /**
129
     * See field {@link VisitorClassMethodDef#anonymousSuperTypeName}
130
     */
131
    @Override
132
    public boolean visit(AnonymousClassDeclaration node) {
133
        org.moosetechnology.model.famix.famixjavaentities.Type fmx;
134
        ITypeBinding bnd = (ITypeBinding) StubBinding.getDeclarationBinding(node);
4✔
135

136
        int modifiers = (bnd != null) ? bnd.getModifiers() : EntityDictionary.UNKNOWN_MODIFIERS;
6!
137
        if (bnd.isEnum()) {
3✔
138
            Enum famixEnum = (Enum) context.topType();
5✔
139
            fmx = this.dico.ensureFamixEnum(bnd, Util.stringForAnonymousEnum(bnd.getDeclaringMember().getName(),famixEnum.getName()),/*owner*/famixEnum);
12✔
140
            EnumValue enumValue = this.dico.ensureFamixEnumValue((IVariableBinding) bnd.getDeclaringMember(), bnd.getDeclaringMember().getName(), famixEnum);
11✔
141
            this.dico.ensureFamixEntityTyping(bnd,enumValue,fmx);
7✔
142
        } else {
1✔
143
            fmx = this.dico.ensureFamixClass(bnd, Util.stringForAnonymousName(getAnonymousSuperTypeName(), context), (ContainerEntity) /*owner*/context.top(),
16✔
144
                    /*isGeneric*/false, modifiers);
145
        }
146

147
        if (fmx != null) {
2!
148
            Util.recursivelySetIsStub(fmx, false);
3✔
149

150
            if (options.withAnchors()) {
4!
151
                dico.addSourceAnchor(fmx, node);
6✔
152
            }
153
            this.context.pushType(fmx);
4✔
154
            return super.visit(node);
4✔
155
        } else {
NEW
156
            return false;
×
157
        }
158
    }
159

160
    @Override
161
    public void endVisit(AnonymousClassDeclaration node) {
162
        if (!anonymousSuperTypeName.empty()) {
4✔
163
            anonymousSuperTypeName.pop();
4✔
164
        }
165
        this.context.popType();
4✔
166
        super.endVisit(node);
3✔
167
    }
1✔
168

169
    @Override
170
    public boolean visit(EnumDeclaration node) {
171
//                System.err.println("TRACE, Visiting EnumDeclaration: "+node.getName().getIdentifier());
172
        ITypeBinding bnd = (ITypeBinding) StubBinding.getDeclarationBinding(node);
4✔
173

174
        org.moosetechnology.model.famix.famixjavaentities.Enum fmx = dico.ensureFamixEnum(bnd, node.getName().getIdentifier(), (TWithTypes) context.top());
12✔
175
        if (fmx != null) {
2!
176
            Util.recursivelySetIsStub(fmx, false);
3✔
177

178
            this.context.pushType(fmx);
4✔
179
            if (options.withAnchors()) {
4!
180
                dico.addSourceAnchor(fmx, node);
6✔
181
            }
182
            return super.visit(node);
4✔
183
        } else {
NEW
184
            return false;
×
185
        }
186
    }
187

188
    @Override
189
    public void endVisit(EnumDeclaration node) {
190
        this.context.popType();
4✔
191
        super.endVisit(node);
3✔
192
    }
1✔
193

194
    @Override
195
    public boolean visit(AnnotationTypeDeclaration node) {
196
        ITypeBinding bnd = node.resolveBinding();
3✔
197
        AnnotationType fmx = dico.ensureFamixAnnotationType(bnd, node.getName().getIdentifier(), (ContainerEntity) context.top());
12✔
198
        if (fmx != null) {
2!
199
            Util.recursivelySetIsStub(fmx, false);
3✔
200
            if (options.withAnchors()) {
4!
201
                dico.addSourceAnchor(fmx, node);
6✔
202
            }
203

204
            context.pushType(fmx);
4✔
205
            return super.visit(node);
4✔
206
        } else {
NEW
207
            context.pushType(null);
×
NEW
208
            return false;
×
209
        }
210
    }
211

212
    @Override
213
    public void endVisit(AnnotationTypeDeclaration node) {
214
        this.context.popType();
4✔
215
        super.endVisit(node);
3✔
216
    }
1✔
217

218
    /**
219
     * MethodDeclaration ::=
220
     * [ Javadoc ] { ExtendedModifier } [ &lt; TypeParameter { , TypeParameter } &gt; ] ( Type | void )
221
     * Identifier (
222
     * [ ReceiverParameter , ] [ FormalParameter { , FormalParameter } ]
223
     * ) { Dimension }
224
     * [ throws Type { , Type } ]
225
     * ( Block | ; )
226
     * Also includes ConstructorDeclaration (same thing without return type)
227
     * <p>
228
     * Local type: same as {@link VisitorClassMethodDef#visit(ClassInstanceCreation)},
229
     * we create it even if it is a local method because there are too many ways it can access external things
230
     */
231
    @SuppressWarnings("unchecked")
232
    @Override
233
    public boolean visit(MethodDeclaration node) {
234
        IMethodBinding bnd = (IMethodBinding) StubBinding.getDeclarationBinding(node);
4✔
235
        Collection<String> paramTypes = new ArrayList<>();
4✔
236
        for (SingleVariableDeclaration param : (List<SingleVariableDeclaration>) node.parameters()) {
11✔
237
            paramTypes.add(Util.jdtTypeName(param.getType()));
6✔
238
        }
1✔
239

240
        Method fmx = dico.ensureFamixMethod(bnd, node.getName().getIdentifier(), paramTypes,
12✔
241
                /*returnType*/null, (TWithMethods) /*owner*/context.topType(), node.getModifiers());
4✔
242

243
        if (fmx != null) {
2!
244
            fmx.setIsStub(false);
4✔
245
            // fmx.setBodyHash(this.computeHashForMethodBody(node));
246

247
            this.context.pushMethod(fmx);
4✔
248

249
            if (options.withAnchors()) {
4!
250
                dico.addSourceAnchor(fmx, node);
6✔
251
            }
252

253
            if (node.getBody() != null) {
3✔
254
                context.setTopMethodCyclo(1);
5✔
255
            } else {
256
                // In interfaces you don't need to explicitly define the method as abstract
257
                // However, if they don't have a body, they are!
258
                fmx.setIsAbstract(true);
4✔
259
            }
260
            return super.visit(node);
4✔
261
        } else {
NEW
262
            this.context.pushMethod(null);
×
NEW
263
            return false;
×
264
        }
265
    }
266

267
    private String computeHashForMethodBody(MethodDeclaration node) {
NEW
268
        Block body = node.getBody();
×
NEW
269
        if ((body == null) || (md5 == null)) {
×
UNCOV
270
            return "0";
×
271
        }
272
        byte[] bytes = node.getBody().toString().replaceAll("\\r|\\n|\\t", "").getBytes();
×
273

NEW
274
        return DigestUtils.md5Hex(bytes).toUpperCase();
×
275
    }
276

277
    @Override
278
    public void endVisit(MethodDeclaration node) {
279
        closeMethodDeclaration();
2✔
280
        super.endVisit(node);
3✔
281
    }
1✔
282

283
    /**
284
     * BodyDeclaration ::=
285
     * [ ... ]
286
     * FieldDeclaration
287
     * Initializer
288
     * MethodDeclaration (for methods and constructors)
289
     * Initializer ::=
290
     * [ static ] Block
291
     */
292
    @Override
293
    public boolean visit(Initializer node) {
294
        Method fmx = (Method) createInitBlock(Modifier.isStatic(node.getModifiers()), true);
10✔
295
        // init-block don't have return type so no need to create a reference from this class to the "declared return type" class when classSummary is TRUE
296
        // also no parameters specified here, so no references to create either
297

298
        if (fmx != null) {
2!
299
            dico.setMethodModifiers(fmx, node.getModifiers());
6✔
300
            if (options.withAnchors()) {
4!
301
                dico.addSourceAnchor(fmx, node);
6✔
302
            }
303

304
            if (node.getBody() != null) {
3!
305
                context.setTopMethodCyclo(1);
4✔
306
            }
307

308
            return super.visit(node);
4✔
309
        } else {
NEW
310
            this.context.pushMethod(null);   // because endVisit(Initializer) will pop it out
×
NEW
311
            return false;
×
312
        }
313
    }
314

315
    @Override
316
    public void endVisit(Initializer node) {
317
        closeMethodDeclaration();
2✔
318
        super.endVisit(node);
3✔
319
    }
1✔
320

321
    @SuppressWarnings("unchecked")
322
    @Override
323
    public boolean visit(FieldDeclaration node) {
324
        boolean hasInitBlock = false;
2✔
325
        for (VariableDeclaration varDecl : (List<VariableDeclaration>) node.fragments()) {
11✔
326
            if (varDecl.getInitializer() != null) {
3✔
327
                createInitBlock(Modifier.isStatic(node.getModifiers()), false);
9✔
328
                hasInitBlock = true;
2✔
329
                break;  // we created the INIT_BLOCK, no need to look for other declaration that would only ensure the same creation
1✔
330
            }
331
        }
1✔
332
        return hasInitBlock;
2✔
333
    }
334

335
    public void endVisit(FieldDeclaration node) {
336
        closeOptionalInitBlock();
2✔
337
    }
1✔
338

339
    @Override
340
    public boolean visit(AnnotationTypeMemberDeclaration node) {
341
//                System.err.println("TRACE, Visiting AnnotationTypeMemberDeclaration: "+node.getName().getIdentifier());
342
        IMethodBinding bnd = node.resolveBinding();
3✔
343

344
        // note"Annotation members looks like methods, but they are closer to attributes
345
        AnnotationTypeAttribute fmx = dico.ensureFamixAnnotationTypeAttribute(bnd, node.getName().getIdentifier(), (AnnotationType) context.topType());
12✔
346
        if (fmx != null) {
2!
347
            fmx.setIsStub(false);
4✔
348
            if (options.withAnchors()) {
4!
349
                dico.addSourceAnchor(fmx, node);
6✔
350
            }
351

352
            context.pushAnnotationMember(fmx);
4✔
353
            return super.visit(node);
4✔
354
        } else {
NEW
355
            context.pushAnnotationMember(null);
×
NEW
356
            return false;
×
357
        }
358
    }
359

360
    @Override
361
    public void endVisit(AnnotationTypeMemberDeclaration node) {
362
        this.context.popAnnotationMember();
4✔
363
        super.endVisit(node);
3✔
364
    }
1✔
365

366
    @Override
367
    public boolean visit(ConstructorInvocation node) {
368
        //                System.err.println("TRACE, Visiting ConstructorInvocation: ");
369
        this.context.addTopMethodNOS(1);
4✔
370
        return super.visit(node);
4✔
371
    }
372

373
    @Override
374
    public boolean visit(SuperConstructorInvocation node) {
375
        this.context.addTopMethodNOS(1);
4✔
376
        return super.visit(node);
4✔
377
    }
378

379
    // "SomeClass.class"
380
    public boolean visit(TypeLiteral node) {
381
        dico.ensureFamixMetaClass(null);
5✔
382
        return false;
2✔
383
    }
384

385
    @Override
386
    public boolean visit(ThrowStatement node) {
387
        this.context.addTopMethodNOS(1);
4✔
388
        return super.visit(node);
4✔
389
    }
390

391
    @Override
392
    public boolean visit(CatchClause node) {
393
        this.context.addTopMethodCyclo(1);
4✔
394
        return super.visit(node);
4✔
395
    }
396

397
    @Override
398
    public boolean visit(AssertStatement node) {
NEW
399
        this.context.addTopMethodNOS(1);
×
NEW
400
        return super.visit(node);
×
401
    }
402

403
    @Override
404
    public boolean visit(Assignment node) {
405
        this.context.addTopMethodNOS(1);
4✔
406
        return super.visit(node);
4✔
407
    }
408

409
    @Override
410
    public boolean visit(ContinueStatement node) {
NEW
411
        this.context.addTopMethodNOS(1);
×
NEW
412
        return super.visit(node);
×
413
    }
414

415
    @Override
416
    public boolean visit(DoStatement node) {
NEW
417
        this.context.addTopMethodCyclo(1);
×
NEW
418
        this.context.addTopMethodNOS(1);
×
NEW
419
        return super.visit(node);
×
420
    }
421

422
    @Override
423
    public boolean visit(ExpressionStatement node) {
424
        this.context.addTopMethodNOS(1);
4✔
425
        return super.visit(node);
4✔
426
    }
427

428
    @Override
429
    public boolean visit(EnhancedForStatement node) {
430
        this.context.addTopMethodCyclo(1);
4✔
431
        this.context.addTopMethodNOS(1);
4✔
432
        return super.visit(node);
4✔
433
    }
434

435
    @Override
436
    public boolean visit(ForStatement node) {
437
        this.context.addTopMethodCyclo(1);
4✔
438
        this.context.addTopMethodNOS(1);
4✔
439
        return super.visit(node);
4✔
440
    }
441

442
    @Override
443
    public boolean visit(IfStatement node) {
444
        this.context.addTopMethodCyclo(1);
4✔
445
        this.context.addTopMethodNOS(1);
4✔
446
        return super.visit(node);
4✔
447
    }
448

449
    @Override
450
    public boolean visit(ReturnStatement node) {
451
        this.context.addTopMethodNOS(1);
4✔
452
        return super.visit(node);
4✔
453
    }
454

455
    @Override
456
    public boolean visit(SwitchCase node) {
457
        this.context.addTopMethodCyclo(1);
4✔
458
        this.context.addTopMethodNOS(1);
4✔
459
        return super.visit(node);
4✔
460
    }
461

462
    @Override
463
    public boolean visit(SwitchStatement node) {
464
        this.context.addTopMethodNOS(1);
4✔
465
        return super.visit(node);
4✔
466
    }
467

468
    @Override
469
    public boolean visit(SynchronizedStatement node) {
NEW
470
        this.context.addTopMethodNOS(1);
×
NEW
471
        return super.visit(node);
×
472
    }
473

474
    @Override
475
    public boolean visit(TryStatement node) {
476
        this.context.addTopMethodCyclo(1);
4✔
477
        this.context.addTopMethodNOS(1);
4✔
478
        return super.visit(node);
4✔
479
    }
480

481
    @Override
482
    public boolean visit(VariableDeclarationStatement node) {
483
        this.context.addTopMethodNOS(1);
4✔
484
        return super.visit(node);
4✔
485
    }
486

487
    @Override
488
    public boolean visit(WhileStatement node) {
489
        this.context.addTopMethodCyclo(1);
4✔
490
        this.context.addTopMethodNOS(1);
4✔
491
        return super.visit(node);
4✔
492
    }
493

494
    // UTILITY METHODS
495

496
    /**
497
     * REnsures the creation of the fake method: {@link EntityDictionary#INIT_BLOCK_NAME}
498
     * <p>
499
     * Used in the case of instance/class initializer and initializing expressions of FieldDeclarations and EnumConstantDeclarations
500
     */
501
    protected TMethod createInitBlock(Boolean isStatic, Boolean isInitializationBlock) {
502
        // putting field's initialization code in an INIT_BLOCK_NAME method
503
        Method ctxtMeth = (Method) this.context.topMethod();
5✔
504
        if (ctxtMeth != null && !ctxtMeth.getIsInitializer() && ctxtMeth.getIsClassSide() != isStatic) {
10!
NEW
505
            ctxtMeth = null;
×
506
        } else {
507
            if (ctxtMeth != null && ctxtMeth.getParentType() != context.topType()) {
8!
508
                /* We are in a field initialization in an anonymous class created in another field initialization.
509
                 * In this example, we are in the declaration of aField2:
510
                 * class Class1 {
511
                 *         class Class2 {}
512
                 *  Class2 aField1 = new Class2() {
513
                 *                 Class3 aField2 = xyz;
514
                 *   }
515
                 * }
516
                 *
517
                 * This means we have to create the Initializer of the inner class (in the example above, Class2::<Initializer>).
518
                 */
519
                ctxtMeth = null;
2✔
520
            }
521
        }
522
        if (ctxtMeth == null) {
2!
523
            ctxtMeth = dico.ensureFamixInitializer((TWithMethods) context.topType(), isStatic, isInitializationBlock);
10✔
524
            ctxtMeth.setIsStub(false);
4✔
525
            ctxtMeth.setIsDead(false);
3✔
526
            // initialization block doesn't have return type so no need to create a reference from its class to the "declared return type" class when classSummary is TRUE
527
            pushInitBlockMethod(ctxtMeth);
3✔
528
        }
529
        return ctxtMeth;
2✔
530
    }
531

532
    /**
533
     * Special method InitBlock may be "created" in various steps,
534
     * mainly when attributes are declared+initialized with the result of a method call.<br>
535
     * In such a case, we need to recover the previous metric values to add to them
536
     *
537
     * @param ctxtMeth -- the InitBlock FamixMethod
538
     */
539
    protected void pushInitBlockMethod(TMethod ctxtMeth) {
540
        int nos = (ctxtMeth.getNumberOfStatements() == null) ? 0 : ctxtMeth.getNumberOfStatements().intValue();
9✔
541
        int cyclo = (ctxtMeth.getCyclomaticComplexity() == null) ? 0 : ctxtMeth.getCyclomaticComplexity().intValue();
9✔
542
        this.context.pushMethod(ctxtMeth);
4✔
543
        if ((nos != 0) || (cyclo != 0)) {
4!
544
            context.setTopMethodNOS(nos);
4✔
545
            context.setTopMethodCyclo(cyclo);
4✔
546
        }
547
    }
1✔
548

549
    protected void closeOptionalInitBlock() {
550
        Method ctxtMeth = (Method) this.context.topMethod();
5✔
551
        if ((ctxtMeth != null) && ctxtMeth.getIsInitializer() && !ctxtMeth.getIsConstructor()) {
10!
552
            closeMethodDeclaration();
2✔
553
        }
554
    }
1✔
555

556
    /**
557
     * When closing a method declaration, we need to take care of some metrics that are also collected
558
     */
559
    protected void closeMethodDeclaration() {
560
        if (context.topMethod() != null) {
4!
561
            int cyclo = context.getTopMethodCyclo();
4✔
562
            int nos = context.getTopMethodNOS();
4✔
563
            Method fmx = (Method) this.context.popMethod();
5✔
564
            if (fmx != null) {
2!
565
                fmx.setNumberOfStatements(nos);
4✔
566
                fmx.setCyclomaticComplexity(cyclo);
4✔
567
            }
568
        }
569
    }
1✔
570

571
}
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