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

IntelPython / dpctl / 14311659303

07 Apr 2025 02:19PM UTC coverage: 86.321% (-0.06%) from 86.379%
14311659303

Pull #2038

github

web-flow
Merge 1ea6b9cc2 into f75b04ca5
Pull Request #2038: Add support for raw_kernel_arg extension

3018 of 3716 branches covered (81.22%)

Branch coverage included in aggregate %.

52 of 71 new or added lines in 3 files covered. (73.24%)

21 existing lines in 2 files now uncovered.

12165 of 13873 relevant lines covered (87.69%)

7004.62 hits per line

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

85.95
/dpctl/_sycl_platform.pyx
1
#                      Data Parallel Control (dpctl)
1✔
2
#
3
# Copyright 2020-2025 Intel Corporation
4
#
5
# Licensed under the Apache License, Version 2.0 (the "License");
6
# you may not use this file except in compliance with the License.
7
# You may obtain a copy of the License at
8
#
9
#    http://www.apache.org/licenses/LICENSE-2.0
10
#
11
# Unless required by applicable law or agreed to in writing, software
12
# distributed under the License is distributed on an "AS IS" BASIS,
13
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
# See the License for the specific language governing permissions and
15
# limitations under the License.
16
#
17
# distutils: language = c++
18
# cython: language_level=3
19
# cython: linetrace=True
20

21
""" Implements SyclPlatform Cython extension type.
22
"""
23

24
from libcpp cimport bool
25

26
from ._backend cimport (  # noqa: E211
27
    DPCTLCString_Delete,
28
    DPCTLDeviceSelector_Delete,
29
    DPCTLDeviceVector_Delete,
30
    DPCTLDeviceVector_GetAt,
31
    DPCTLDeviceVector_Size,
32
    DPCTLDeviceVectorRef,
33
    DPCTLFilterSelector_Create,
34
    DPCTLPlatform_AreEq,
35
    DPCTLPlatform_Copy,
36
    DPCTLPlatform_Create,
37
    DPCTLPlatform_CreateFromSelector,
38
    DPCTLPlatform_Delete,
39
    DPCTLPlatform_GetBackend,
40
    DPCTLPlatform_GetCompositeDevices,
41
    DPCTLPlatform_GetDefaultContext,
42
    DPCTLPlatform_GetDevices,
43
    DPCTLPlatform_GetName,
44
    DPCTLPlatform_GetPlatforms,
45
    DPCTLPlatform_GetVendor,
46
    DPCTLPlatform_GetVersion,
47
    DPCTLPlatform_Hash,
48
    DPCTLPlatformMgr_GetInfo,
49
    DPCTLPlatformMgr_PrintInfo,
50
    DPCTLPlatformVector_Delete,
51
    DPCTLPlatformVector_GetAt,
52
    DPCTLPlatformVector_Size,
53
    DPCTLPlatformVectorRef,
54
    DPCTLSyclContextRef,
55
    DPCTLSyclDeviceRef,
56
    DPCTLSyclDeviceSelectorRef,
57
    DPCTLSyclPlatformRef,
58
    _backend_type,
59
    _device_type,
60
)
61

62
import warnings
1✔
63

64
from ._sycl_context import SyclContextCreationError
1✔
65
from .enum_types import backend_type
1✔
66
from .enum_types import device_type as device_type_t
1✔
67

68
from ._sycl_context cimport SyclContext
69
from ._sycl_device cimport SyclDevice
70

71
__all__ = [
1✔
72
    "get_platforms",
73
    "lsplatform",
74
    "SyclPlatform",
75
]
76

77
cdef class _SyclPlatform:
78
    """ Data owner for SyclPlatform
79
    """
80

81
    def __dealloc__(self):
82
        DPCTLPlatform_Delete(self._platform_ref)
1✔
83
        DPCTLCString_Delete(self._name)
1✔
84
        DPCTLCString_Delete(self._vendor)
1✔
85
        DPCTLCString_Delete(self._version)
1✔
86

87

88
cdef void _init_helper(_SyclPlatform platform, DPCTLSyclPlatformRef PRef):
1✔
89
    "Populate attributes of class from opaque reference PRef"
90
    platform._platform_ref = PRef
1✔
91
    platform._name = DPCTLPlatform_GetName(PRef)
1✔
92
    platform._version = DPCTLPlatform_GetVersion(PRef)
1✔
93
    platform._vendor = DPCTLPlatform_GetVendor(PRef)
1✔
94

95

96
cdef class SyclPlatform(_SyclPlatform):
97
    """ SyclPlatform(self, arg=None)
98
    Python class representing ``sycl::platform`` class.
99

100
    There are two ways of creating a :class:`.SyclPlatform`
101
    instance:
102

103
    - Invoking the constructor with no arguments creates a
104
      platform using the default selector.
105

106
    :Example:
107
        .. code-block:: python
108

109
            import dpctl
110

111
            # Create a SyclPlatform for default-selected device
112
            pl = dpctl.SyclPlatform()
113
            print(pl.name, pl.version)
114

115
    - Invoking the constructor with specific filter selector string that
116
      creates a queue for the device corresponding to the filter string.
117

118
    :Example:
119
        .. code-block:: python
120

121
            import dpctl
122

123
            # Create a SyclPlatform for device selected by
124
            # filter-selector string
125
            pl = dpctl.SyclPlatform("opencl:cpu")
126
            print(pl.name, pl.version)
127
    """
128

129
    @staticmethod
130
    cdef SyclPlatform _create(DPCTLSyclPlatformRef pref):
1✔
131
        """
132
        This function calls ``DPCTLPlatform_Delete(pref)``.
133

134
        The user of this function must pass a copy to keep the
135
        pref argument alive.
136
        """
137
        cdef _SyclPlatform p = _SyclPlatform.__new__(_SyclPlatform)
1✔
138
        # Initialize the attributes of the SyclPlatform object
139
        _init_helper(<_SyclPlatform>p, pref)
1✔
140
        return SyclPlatform(p)
1✔
141

142
    cdef int _init_from__SyclPlatform(self, _SyclPlatform other):
1✔
143
        self._platform_ref = DPCTLPlatform_Copy(other._platform_ref)
1✔
144
        if (self._platform_ref is NULL):
1✔
145
            return -1
×
146
        self._name = DPCTLPlatform_GetName(self._platform_ref)
1✔
147
        self._version = DPCTLPlatform_GetVersion(self._platform_ref)
1✔
148
        self._vendor = DPCTLPlatform_GetVendor(self._platform_ref)
1✔
149

150
    cdef int _init_from_cstring(self, const char *string):
1✔
151
        cdef DPCTLSyclDeviceSelectorRef DSRef = NULL
1✔
152
        DSRef = DPCTLFilterSelector_Create(string)
1✔
153
        ret = self._init_from_selector(DSRef)
1✔
154
        return ret
1✔
155

156
    cdef int _init_from_selector(self, DPCTLSyclDeviceSelectorRef DSRef):
1✔
157
        # Initialize the SyclPlatform from a DPCTLSyclDeviceSelectorRef
158
        cdef DPCTLSyclPlatformRef PRef = DPCTLPlatform_CreateFromSelector(DSRef)
1✔
159
        DPCTLDeviceSelector_Delete(DSRef)
1✔
160
        if PRef is NULL:
1✔
161
            return -1
1✔
162
        else:
163
            _init_helper(self, PRef)
1✔
164
            return 0
1✔
165

166
    cdef DPCTLSyclPlatformRef get_platform_ref(self):
1✔
167
        """ Returns the ``DPCTLSyclPlatformRef`` pointer for this class.
168
        """
169
        return self._platform_ref
1✔
170

171
    @property
172
    def __name__(self):
173
        return "SyclPlatform"
1✔
174

175
    def __repr__(self):
176
        return (
1✔
177
            "<dpctl."
178
            + self.__name__
1✔
179
            + " ["
1✔
180
            + self.name
1✔
181
            + ", "
1✔
182
            + self.vendor
1✔
183
            + ", "
1✔
184
            + self.version + "] at {}>".format(hex(id(self)))
1✔
185
        )
186

187
    def __cinit__(self, arg=None):
188
        if type(arg) is unicode:
1✔
189
            string = bytes(<unicode>arg, "utf-8")
1✔
190
            filter_c_str = string
1✔
191
            ret = self._init_from_cstring(filter_c_str)
1✔
192
            if ret == -1:
1✔
193
                raise ValueError(
1✔
194
                    "Could not create a SyclPlatform with the selector string"
195
                )
196
        elif isinstance(arg, unicode):
1✔
UNCOV
197
            string = bytes(<unicode>unicode(arg), "utf-8")
×
198
            filter_c_str = string
×
199
            ret = self._init_from_cstring(filter_c_str)
×
200
            if ret == -1:
×
201
                raise ValueError(
×
202
                    "Could not create a SyclPlatform with the selector string"
203
                )
204
        elif isinstance(arg, _SyclPlatform):
1✔
205
            ret = self._init_from__SyclPlatform(arg)
1✔
206
            if ret == -1:
1✔
UNCOV
207
                raise ValueError(
×
208
                    "Could not create SyclPlatform from _SyclPlatform instance"
209
                )
210
        elif arg is None:
1✔
211
            PRef = DPCTLPlatform_Create()
1✔
212
            if PRef is NULL:
1✔
UNCOV
213
                raise ValueError(
×
214
                    "Could not create a SyclPlatform from default selector"
215
                )
216
            else:
217
                _init_helper(self, PRef)
1✔
218
        else:
UNCOV
219
            raise ValueError(
×
220
                "Invalid argument. Argument should be a str object specifying "
221
                "a SYCL filter selector string."
222
            )
223

224
    def print_platform_info(self, verbosity=0):
1✔
225
        """ Print information about the SYCL platform.
226

227
        The level of information printed out by the function can be controlled
228
        by the optional ``vebosity`` setting.
229

230
        - **Verbosity level 0**: Prints out the list of platforms and their
231
          names.
232
        - **Verbosity level 1**: Prints out the name, version, vendor,
233
          backend, number of devices for each platform.
234
        - **Verbosity level 2**: At the highest level of verbosity
235
          everything in the previous levels along with the name, version,
236
          and filter string for each device is printed.
237

238
        Args:
239
            verbosity (Literal[0, 1, 2], optional):.
240
                The verbosity controls how much information is printed by the
241
                function. Value ``0`` is the lowest level set by default and ``2``
242
                is the highest level to print the most verbose output.
243
                Default: ``0``
244
        """
245
        cdef size_t v = 0
1✔
246

247
        if not isinstance(verbosity, int):
1✔
UNCOV
248
            warnings.warn(
×
249
                "Illegal verbosity level. Accepted values are 0, 1, or 2. "
250
                "Using the default verbosity level of 0."
251
            )
252
        else:
253
            v = <size_t>(verbosity)
1✔
254
            if v > 2:
1✔
UNCOV
255
                warnings.warn(
×
256
                    "Illegal verbosity level. Accepted values are 0, 1, or 2. "
257
                    "Using the default verbosity level of 0."
258
                )
UNCOV
259
                v = 0
×
260
        DPCTLPlatformMgr_PrintInfo(self._platform_ref, v)
1✔
261

262
    @property
263
    def vendor(self):
264
        """
265
        Returns the platform vendor name as a string.
266

267
        Returns:
268
            str:
269
                Vendor name
270
        """
271
        return self._vendor.decode()
1✔
272

273
    @property
274
    def version(self):
275
        """ Returns a backend-defined driver version as a string.
276

277
        Returns:
278
            str:
279
                Version of the backend-defined driver
280
        """
281
        return self._version.decode()
1✔
282

283
    @property
284
    def name(self):
285
        """ Returns the name of the platform as a string.
286

287
        Returns:
288
            str:
289
                Name of the platform
290
        """
291
        return self._name.decode()
1✔
292

293
    @property
294
    def backend(self):
295
        """Returns the backend_type enum value for this platform
296

297
        Returns:
298
            backend_type:
299
                The backend for the platform.
300
        """
301
        cdef _backend_type BTy = (
302
            DPCTLPlatform_GetBackend(self._platform_ref)
1✔
303
        )
304
        if BTy == _backend_type._CUDA:
1✔
UNCOV
305
            return backend_type.cuda
×
306
        elif BTy == _backend_type._HIP:
UNCOV
307
            return backend_type.hip
×
308
        elif BTy == _backend_type._LEVEL_ZERO:
UNCOV
309
            return backend_type.level_zero
×
310
        elif BTy == _backend_type._OPENCL:
311
            return backend_type.opencl
1✔
312
        else:
UNCOV
313
            raise ValueError("Unknown backend type.")
×
314

315
    @property
316
    def default_context(self):
317
        """Returns the default platform context for this platform
318

319
        Returns:
320
            :class:`dpctl.SyclContext`
321
                The default context for the platform.
322
        Raises:
323
            SyclContextCreationError
324
                If default_context is not supported
325
        """
326
        cdef DPCTLSyclContextRef CRef = (
327
            DPCTLPlatform_GetDefaultContext(self._platform_ref)
1✔
328
        )
329

330
        if (CRef == NULL):
1✔
UNCOV
331
            raise SyclContextCreationError(
×
332
                "Getting default_context ran into a problem"
333
            )
334
        else:
335
            return SyclContext._create(CRef)
1✔
336

337
    cdef bool equals(self, SyclPlatform other):
1✔
338
        """
339
        Returns true if the :class:`dpctl.SyclPlatform` argument has the
340
        same underlying ``DPCTLSyclPlatformRef`` object as this
341
        :class:`dpctl.SyclPlatform` instance.
342

343
        Returns:
344
            bool:
345
                ``True`` if the two :class:`dpctl.SyclPlatform` objects
346
                point to the same ``DPCTLSyclPlatformRef`` object, otherwise
347
                ``False``.
348
        """
349
        return DPCTLPlatform_AreEq(self._platform_ref, other.get_platform_ref())
1✔
350

351
    def __eq__(self, other):
352
        """
353
        Returns True if the :class:`dpctl.SyclPlatform` argument has the
354
        same underlying ``DPCTLSyclPlatformRef`` object as this
355
        :class:`dpctl.SyclPlatform` instance.
356

357
        Returns:
358
            bool:
359
                ``True`` if the two :class:`dpctl.SyclPlatform` objects
360
                point to the same ``DPCTLSyclPlatformRef`` object, otherwise
361
                ``False``.
362
        """
363
        if isinstance(other, SyclPlatform):
1✔
364
            return self.equals(<SyclPlatform> other)
1✔
365
        else:
UNCOV
366
            return False
×
367

368
    def __hash__(self):
369
        """
370
        Returns a hash value by hashing the underlying ``sycl::platform`` object.
371

372
        Returns:
373
            int:
374
                Hash value
375
        """
376
        return DPCTLPlatform_Hash(self._platform_ref)
1✔
377

378
    def get_devices(self, device_type=device_type_t.all):
1✔
379
        """
380
        Returns the list of :class:`dpctl.SyclDevice` objects associated with
381
        :class:`dpctl.SyclPlatform` instance selected based on
382
        the given :class:`dpctl.device_type`.
383

384
        Args:
385
            device_type (str, :class:`dpctl.device_type`, optional):
386
                A :class:`dpctl.device_type` enum value or a string that
387
                specifies a SYCL device type. Currently, accepted values are:
388
                "gpu", "cpu", "accelerator", or "all", and their equivalent
389
                ``dpctl.device_type`` enumerators.
390
                Default: ``dpctl.device_type.all``.
391

392
        Returns:
393
            list:
394
                A :obj:`list` of :class:`dpctl.SyclDevice` objects
395
                that belong to this platform.
396

397
        Raises:
398
            TypeError:
399
                If `device_type` is not a string or :class:`dpctl.device_type`
400
                enum.
401
            ValueError:
402
                If the ``DPCTLPlatform_GetDevices`` call returned
403
                ``NULL`` instead of a ``DPCTLDeviceVectorRef`` object.
404
        """
405
        cdef _device_type DTy = _device_type._ALL_DEVICES
1✔
406
        cdef DPCTLDeviceVectorRef DVRef = NULL
1✔
407
        cdef size_t num_devs
408
        cdef size_t i
409
        cdef DPCTLSyclDeviceRef DRef
410

411
        if isinstance(device_type, str):
1✔
412
            dty_str = device_type.strip().lower()
1✔
413
            if dty_str == "accelerator":
1✔
414
                DTy = _device_type._ACCELERATOR
1✔
415
            elif dty_str == "all":
1✔
416
                DTy = _device_type._ALL_DEVICES
1✔
417
            elif dty_str == "cpu":
1✔
418
                DTy = _device_type._CPU
1✔
419
            elif dty_str == "gpu":
1✔
420
                DTy = _device_type._GPU
1✔
421
            else:
UNCOV
422
                DTy = _device_type._UNKNOWN_DEVICE
×
423
        elif isinstance(device_type, device_type_t):
1✔
424
            if device_type == device_type_t.all:
1✔
425
                DTy = _device_type._ALL_DEVICES
1✔
426
            elif device_type == device_type_t.accelerator:
1✔
427
                DTy = _device_type._ACCELERATOR
1✔
428
            elif device_type == device_type_t.cpu:
1✔
429
                DTy = _device_type._CPU
1✔
430
            elif device_type == device_type_t.gpu:
1✔
431
                DTy = _device_type._GPU
1✔
432
            else:
UNCOV
433
                DTy = _device_type._UNKNOWN_DEVICE
×
434
        else:
UNCOV
435
            raise TypeError(
×
436
                "device type should be specified as a str or an "
437
                "``enum_types.device_type``."
438
            )
439
        DVRef = DPCTLPlatform_GetDevices(self.get_platform_ref(), DTy)
1✔
440
        if (DVRef is NULL):
1✔
UNCOV
441
            raise ValueError("Internal error: NULL device vector encountered")
×
442
        num_devs = DPCTLDeviceVector_Size(DVRef)
1✔
443
        devices = []
1✔
444
        for i in range(num_devs):
1✔
445
            DRef = DPCTLDeviceVector_GetAt(DVRef, i)
1✔
446
            devices.append(SyclDevice._create(DRef))
1✔
447
        DPCTLDeviceVector_Delete(DVRef)
1✔
448

449
        return devices
1✔
450

451
    def get_composite_devices(self):
1✔
452
        """
453
        Returns the list of composite :class:`dpctl.SyclDevice` objects
454
        associated with :class:`dpctl.SyclPlatform` instance.
455

456
        Returns:
457
            list:
458
                A :obj:`list` of composite :class:`dpctl.SyclDevice` objects
459
                that belong to this platform.
460

461
        Raises:
462
            ValueError:
463
                If the ``DPCTLPlatform_GetCompositeDevices`` call returned
464
                ``NULL`` instead of a ``DPCTLDeviceVectorRef`` object.
465
        """
466
        cdef DPCTLDeviceVectorRef DVRef = NULL
1✔
467
        cdef size_t num_devs
468
        cdef size_t i
469
        cdef DPCTLSyclDeviceRef DRef
470

471
        DVRef = DPCTLPlatform_GetCompositeDevices(self.get_platform_ref())
1✔
472
        if (DVRef is NULL):
1✔
UNCOV
473
            raise ValueError("Internal error: NULL device vector encountered")
×
474
        num_devs = DPCTLDeviceVector_Size(DVRef)
1✔
475
        composite_devices = []
1✔
476
        for i in range(num_devs):
1✔
UNCOV
477
            DRef = DPCTLDeviceVector_GetAt(DVRef, i)
×
478
            composite_devices.append(SyclDevice._create(DRef))
×
479
        DPCTLDeviceVector_Delete(DVRef)
1✔
480

481
        return composite_devices
1✔
482

483

484
def lsplatform(verbosity=0):
1✔
485
    """
486
    Prints out the list of available SYCL platforms, and optionally extra
487
    metadata about each platform.
488

489
    The level of information printed out by the function can be controlled by
490
    the optional ``vebosity`` setting.
491

492
    - **Verbosity level 0**: Prints out the list of platforms and their names.
493
    - **Verbosity level 1**: Prints out the name, version, vendor, backend,
494
      number of devices for each platform.
495
    - **Verbosity level 2**: At the highest level of verbosity everything in the
496
      previous levels along with the name, version, and filter string for each
497
      device is printed.
498

499
    At verbosity level 2 (highest level) the following output is generated.
500

501
    :Example:
502
        On a system with an OpenCL CPU driver, OpenCL GPU driver,
503
        Level Zero GPU driver, running the command:
504

505
        .. code-block:: bash
506

507
            $ python -c "import dpctl; dpctl.lsplatform(verbosity=2)"
508

509
        outputs
510

511
        .. code-block:: text
512
            :caption: Sample output of lsplatform(verbosity=2)
513

514
            Platform 0::
515
                Name        Intel(R) OpenCL
516
                Version     OpenCL 2.1 LINUX
517
                Vendor      Intel(R) Corporation
518
                Profile     FULL_PROFILE
519
                Backend     opencl
520
                Devices     1
521
                    Device 0::
522
                    Name            Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz
523
                    Driver version  2020.11.11.0.13_160000
524
                    Device type     cpu
525
            Platform 1::
526
                Name        Intel(R) OpenCL HD Graphics
527
                Version     OpenCL 3.0
528
                Vendor      Intel(R) Corporation
529
                Profile     FULL_PROFILE
530
                Backend     opencl
531
                Devices     1
532
                    Device 0::
533
                    Name            Intel(R) Graphics Gen9 [0x3e98]
534
                    Driver version  20.47.18513
535
                    Device type     gpu
536
            Platform 2::
537
                Name        Intel(R) Level-Zero
538
                Version     1.0
539
                Vendor      Intel(R) Corporation
540
                Profile     FULL_PROFILE
541
                Backend     level_zero
542
                Devices     1
543
                    Device 0::
544
                    Name            Intel(R) Graphics Gen9 [0x3e98]
545
                    Driver version  1.0.18513
546
                    Device type     gpu
547

548
    Args:
549
        verbosity (Literal[0,1,2], optional):
550
            The verbosity controls how much information is printed by the
551
            function. 0 is the lowest level set by default and 2 is the highest
552
            level to print the most verbose output. Default: `0`.
553
    """
554
    cdef DPCTLPlatformVectorRef PVRef = NULL
1✔
555
    cdef size_t v = 0
1✔
556
    cdef size_t size = 0
1✔
557
    cdef const char * info_str = NULL
1✔
558
    cdef DPCTLSyclPlatformRef PRef = NULL
1✔
559

560
    if not isinstance(verbosity, int):
1✔
UNCOV
561
        warnings.warn(
×
562
            "Illegal verbosity level. Accepted values are 0, 1, or 2. "
563
            "Using the default verbosity level of 0."
564
        )
565
    else:
566
        v = <size_t>(verbosity)
1✔
567
        if v > 2:
1✔
568
            warnings.warn(
1✔
569
                "Illegal verbosity level. Accepted values are 0, 1, or 2. "
570
                "Using the default verbosity level of 0."
571
            )
572
            v = 0
1✔
573

574
    PVRef = DPCTLPlatform_GetPlatforms()
1✔
575

576
    if PVRef is not NULL:
1✔
577
        size = DPCTLPlatformVector_Size(PVRef)
1✔
578
        for i in range(size):
1✔
579
            if v != 0:
1✔
580
                print("Platform ", i, "::")
1✔
581
            PRef = DPCTLPlatformVector_GetAt(PVRef, i)
1✔
582
            info_str = DPCTLPlatformMgr_GetInfo(PRef,v)
1✔
583
            py_info = <bytes> info_str
1✔
584
            DPCTLCString_Delete(info_str)
1✔
585
            DPCTLPlatform_Delete(PRef)
1✔
586
            print(py_info.decode("utf-8"),end='')
1✔
587
    DPCTLPlatformVector_Delete(PVRef)
1✔
588

589

590
cpdef list get_platforms():
1✔
591
    """
592
    Returns a list of all available SYCL platforms on the system.
593

594
    Returns:
595
        List[:class:`.SyclPlatform`]:
596
            A list of SYCL platforms on the system.
597
    """
598
    cdef list platforms = []
1✔
599
    cdef DPCTLPlatformVectorRef PVRef = NULL
1✔
600
    cdef size_t size = 0
1✔
601

602
    PVRef = DPCTLPlatform_GetPlatforms()
1✔
603
    if PVRef is not NULL:
1✔
604
        size = DPCTLPlatformVector_Size(PVRef)
1✔
605
        for i in range(size):
1✔
606
            PRef = DPCTLPlatformVector_GetAt(PVRef, i)
1✔
607
            P = SyclPlatform._create(PRef)
1✔
608
            platforms.append(P)
1✔
609

610
    DPCTLPlatformVector_Delete(PVRef)
1✔
611
    return platforms
1✔
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