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

WassimTenachi / PhySO / #15

10 Jun 2024 12:47AM UTC coverage: 80.984% (+28.9%) from 52.052%
#15

push

coveralls-python

WassimTenachi
monitoring test

26 of 27 new or added lines in 1 file covered. (96.3%)

131 existing lines in 18 files now uncovered.

6814 of 8414 relevant lines covered (80.98%)

0.81 hits per line

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

93.66
/physo/physym/tests/dataset_UnitTest.py
1
import unittest
1✔
2
import numpy as np
1✔
3
import torch
1✔
4

5
from physo.physym import dataset
1✔
6
from physo.physym import library as Lib
1✔
7
from physo.physym.functions import data_conversion, data_conversion_inv
1✔
8

9

10
class TestDataset(unittest.TestCase):
1✔
11

12
    def test_Dataset_assertions(self):
1✔
13

14
        DEVICE = 'cpu'
1✔
15
        if torch.cuda.is_available():
1✔
16
            DEVICE = 'cuda'
×
17

18
        # DATA
19
        N = int(1e6)
1✔
20
        x = data_conversion  (np.linspace(0.04, 4, N)  ).to(DEVICE)
1✔
21
        v = data_conversion  (np.linspace(0.10, 10, N) ).to(DEVICE)
1✔
22
        t = data_conversion  (np.linspace(0.06, 6, N)  ).to(DEVICE)
1✔
23
        M  = data_conversion (1e6).to(DEVICE)
1✔
24
        c  = data_conversion (3e8).to(DEVICE)
1✔
25
        pi = data_conversion (np.pi).to(DEVICE)
1✔
26
        const1 = data_conversion (1.).to(DEVICE)
1✔
27

28

29
        X = torch.stack((x, v, t), axis=0)
1✔
30
        y_target = data_conversion  (np.linspace(0.01, 6, N)  ).to(DEVICE)
1✔
31
        y_weights = data_conversion (np.random.rand(N)  ).to(DEVICE)
1✔
32

33
        # LIBRARY CONFIG
34
        args_make_tokens = {
1✔
35
                        # operations
36
                        "op_names"             : "all",  # or ["mul", "neg", "inv", "sin"]
37
                        "use_protected_ops"    : False,
38
                        # input variables
39
                        "input_var_ids"        : {"x" : 0         , "v" : 1          , "t" : 2,        },
40
                        "input_var_units"      : {"x" : [1, 0, 0] , "v" : [1, -1, 0] , "t" : [0, 1, 0] },
41
                        "input_var_complexity" : {"x" : 0.        , "v" : 1.         , "t" : 0.,       },
42
                        # constants
43
                        "constants"            : {"pi" : pi        , "c" : c         , "M" : M         , "const1" : const1    },
44
                        "constants_units"      : {"pi" : [0, 0, 0] , "c" : [1, -1, 0], "M" : [0, 0, 1] , "const1" : [0, 0, 0] },
45
                        "constants_complexity" : {"pi" : 0.        , "c" : 0.        , "M" : 1.        , "const1" : 1.        },
46
                            }
47
        my_lib = Lib.Library(args_make_tokens = args_make_tokens,
1✔
48
                             superparent_units = [1, -2, 1], superparent_name = "y")
49

50
        def make_dataset_for_regular_SR(library, X, y, y_weights=1.):
1✔
51
            my_dataset = dataset.Dataset(multi_X=[X, ], multi_y=[y, ], multi_y_weights=[y_weights, ], library=library)
1✔
52
            return my_dataset
1✔
53

54
        # ------- TEST CREATION -------
55
        try:
1✔
56
            my_dataset = make_dataset_for_regular_SR(library=my_lib, X=X, y=y_target)
1✔
UNCOV
57
        except:
×
UNCOV
58
            self.fail("Dataset creation failed.")
×
59

60
        # ------- ASSERTIONS : FLOAT TYPE -------
61
        with self.assertRaises(AssertionError):
1✔
62
            my_dataset = make_dataset_for_regular_SR(library = my_lib, X = torch.ones((3, 100), dtype=int), y = torch.ones((100,)))
1✔
63
        with self.assertRaises(AssertionError):
1✔
64
            my_dataset = make_dataset_for_regular_SR(library = my_lib, X = torch.ones((3, 100)), y = torch.ones((100,), dtype=int))
1✔
65

66
        # ------- ASSERTIONS : SHAPE -------
67
        with self.assertRaises(AssertionError):
1✔
68
            my_dataset = make_dataset_for_regular_SR(library = my_lib, X = torch.ones((3, 100),), y = torch.ones((200,)))
1✔
69
        with self.assertRaises(AssertionError):
1✔
70
            my_dataset = make_dataset_for_regular_SR(library = my_lib, X = torch.ones((100, 3),), y = torch.ones((100,)))
1✔
71

72
        # ------- ASSERTIONS : VARIABLE ID -------
73
        with self.assertRaises(AssertionError):
1✔
74
            my_dataset = make_dataset_for_regular_SR(library=my_lib, X=torch.ones((1, 100), ), y=torch.ones((100,)))
1✔
75

76
        # ------- ASSERTIONS : ONE REALIZATION -------
77
        my_dataset = make_dataset_for_regular_SR(library=my_lib, X=X, y=y_target, y_weights=y_weights)
1✔
78
        self.assertTrue(my_dataset.n_realizations == 1)
1✔
79

80
        self.assertTrue((my_dataset.multi_X_flatten == X).all())
1✔
81
        self.assertTrue((my_dataset.multi_y_flatten == y_target).all())
1✔
82
        self.assertTrue((my_dataset.multi_y_weights_flatten == y_weights).all())
1✔
83

84
        return None
1✔
85

86
    def test_Dataset_assertions_multi_real(self):
1✔
87

88
        DEVICE = 'cpu'
1✔
89
        if torch.cuda.is_available():
1✔
UNCOV
90
            DEVICE = 'cuda'
×
91

92
        # -------------------------------------- Making fake datasets --------------------------------------
93

94
        multi_X = []
1✔
95
        for n_samples in [90, 100, 110]:
1✔
96
            x1 = np.linspace(0, 10, n_samples)
1✔
97
            x2 = np.linspace(0, 1 , n_samples)
1✔
98
            X = np.stack((x1,x2),axis=0)
1✔
99
            X = torch.tensor(X).to(DEVICE)
1✔
100
            multi_X.append(X)
1✔
101
        multi_X = multi_X*10                         # (n_realizations,) of (n_dim, [n_samples depends on dataset],)
1✔
102

103
        n_samples_per_dataset = np.array([X.shape[1] for X in multi_X])
1✔
104
        n_all_samples = n_samples_per_dataset.sum()
1✔
105
        n_realizations = len(multi_X)
1✔
106
        def flatten_multi_data (multi_data,):
1✔
107
            """
108
            Flattens multiple datasets into a single one for vectorized evaluation.
109
            Parameters
110
            ----------
111
            multi_data : list of length (n_realizations,) of torch.tensor of shape (..., [n_samples depends on dataset],)
112
                List of datasets to be flattened.
113
            Returns
114
            -------
115
            torch.tensor of shape (..., n_all_samples)
116
                Flattened data (n_all_samples = sum([n_samples depends on dataset])).
117
            """
118
            flattened_data = torch.cat(multi_data, axis=-1) # (..., n_all_samples)
1✔
119
            return flattened_data
1✔
120

121
        def unflatten_multi_data (flattened_data):
1✔
122
            """
123
            Unflattens a single data into multiple ones.
124
            Parameters
125
            ----------
126
            flattened_data : torch.tensor of shape (..., n_all_samples)
127
                Flattened data (n_all_samples = sum([n_samples depends on dataset])).
128
            Returns
129
            -------
130
            list of len (n_realizations,) of torch.tensor of shape (..., [n_samples depends on dataset],)
131
                Unflattened data.
132
            """
133
            return list(torch.split(flattened_data, n_samples_per_dataset.tolist(), dim=-1)) # (n_realizations,) of (..., [n_samples depends on dataset],)
1✔
134

135
        y_weights_per_dataset = np.array([0, 0.001, 1.0]*10) # Shows weights work
1✔
136
        #y_weights_per_dataset = np.array([1., 1., 1.]*10)
137
        multi_y_weights = [np.full(shape=(n_samples_per_dataset[i],), fill_value=y_weights_per_dataset[i]) for i in range (n_realizations)]
1✔
138
        multi_y_weights = [torch.tensor(y_weights).to(DEVICE) for y_weights in multi_y_weights]
1✔
139
        y_weights_flatten = flatten_multi_data(multi_y_weights)
1✔
140

141
        multi_X_flatten = flatten_multi_data(multi_X)  # (n_dim, n_all_samples)
1✔
142

143
        # Making fake ideal parameters
144
        # n_spe_params   = 3
145
        # n_class_params = 2
146
        random_shift       = (np.random.rand(n_realizations,3)-0.5)*0.8
1✔
147
        ideal_spe_params   = torch.tensor(np.array([1.123, 0.345, 0.116]) + random_shift) # (n_realizations, n_spe_params,)
1✔
148
        ideal_spe_params   = ideal_spe_params.transpose(0,1)                              # (n_spe_params, n_realizations)
1✔
149
        ideal_class_params = torch.tensor(np.array([1.389, 1.005]))                       # (n_class_params, )
1✔
150

151
        ideal_spe_params_flatten = torch.cat(
1✔
152
            [torch.tile(ideal_spe_params[:,i], (n_samples_per_dataset[i],1)).transpose(0,1) for i in range (n_realizations)], # (n_realizations,) of (n_spe_params, [n_samples depends on dataset],)
153
            axis = 1
154
        ) # (n_spe_params, n_all_samples)
155

156
        ideal_class_params_flatten = torch.tile(ideal_class_params, (n_all_samples,1)).transpose(0,1) # (n_class_params, n_all_samples)
1✔
157

158
        def trial_func (X, params, class_params):
1✔
159
            y = params[0]*torch.exp(-params[1]*X[0])*torch.cos(class_params[0]*X[0]+params[2]) + class_params[1]*X[1]
1✔
160
            return y
1✔
161

162
        y_ideals_flatten = trial_func (multi_X_flatten, ideal_spe_params_flatten, ideal_class_params_flatten) # (n_all_samples,)
1✔
163
        multi_y_target   = unflatten_multi_data(y_ideals_flatten)                                         # (n_realizations,) of (n_samples depends on dataset,)
1✔
164

165
        k0_init = [1.,1.,1.]*10 # np.full(n_realizations, 1.)
1✔
166
        # consts
167
        pi     = data_conversion (np.pi) .to(DEVICE)
1✔
168
        const1 = data_conversion (1.)    .to(DEVICE)
1✔
169

170
        # LIBRARY CONFIG
171
        args_make_tokens = {
1✔
172
                        # operations
173
                        "op_names"             : "all",
174
                        "use_protected_ops"    : True,
175
                        # input variables
176
                        "input_var_ids"        : {"t" : 0         , "l" : 1          },
177
                        "input_var_units"      : {"t" : [1, 0, 0] , "l" : [0, 1, 0]  },
178
                        "input_var_complexity" : {"t" : 0.        , "l" : 1.         },
179
                        # constants
180
                        "constants"            : {"pi" : pi        , "const1" : const1    },
181
                        "constants_units"      : {"pi" : [0, 0, 0] , "const1" : [0, 0, 0] },
182
                        "constants_complexity" : {"pi" : 1.        , "const1" : 1.        },
183
                        # free constants
184
                        "class_free_constants"            : {"c0"              , "c1"               },
185
                        "class_free_constants_init_val"   : {"c0" : 1.         , "c1"  : 1.         },
186
                        "class_free_constants_units"      : {"c0" : [-1, 0, 0] , "c1"  : [0, -1, 0] },
187
                        "class_free_constants_complexity" : {"c0" : 1.         , "c1"  : 1.         },
188
                        # free constants
189
                        "spe_free_constants"            : {"k0"              , "k1"               , "k2"               },
190
                        "spe_free_constants_init_val"   : {"k0" : k0_init    , "k1"  : 1.         , "k2"  : 1.         },
191
                        "spe_free_constants_units"      : {"k0" : [0, 0, 0]  , "k1"  : [-1, 0, 0] , "k2"  : [0, 0, 0]  },
192
                        "spe_free_constants_complexity" : {"k0" : 1.         , "k1"  : 1.         , "k2"  : 1.         },
193
                           }
194
        my_lib = Lib.Library(args_make_tokens = args_make_tokens,
1✔
195
                             superparent_units = [0, 0, 0], superparent_name = "y")
196

197
        n_realizations = len(multi_X)
1✔
198

199
        # ------- TEST CREATION -------
200
        try:
1✔
201
            my_dataset = dataset.Dataset(multi_X=multi_X, multi_y=multi_y_target, library=my_lib)
1✔
202
        except:
×
203
            self.fail("Dataset creation failed.")
×
204

205
        # ------- TESTS -------
206

207
        # Wrong number of realizations between X and y_target
208
        with self.assertRaises(AssertionError):
1✔
209
            my_dataset = dataset.Dataset(multi_X=multi_X, multi_y=multi_y_target[:-1], library=my_lib)
1✔
210
        with self.assertRaises(AssertionError):
1✔
211
            my_dataset = dataset.Dataset(multi_X=multi_X[:-1], multi_y=multi_y_target, library=my_lib)
1✔
212

213
        # Sending data for one realization only / sending tensor type
214
        with self.assertRaises(AssertionError):
1✔
215
            my_dataset = dataset.Dataset(multi_X=multi_X[0], multi_y=multi_y_target[0], library=my_lib)
1✔
216

217
        # Test number of realizations
218
        my_dataset = dataset.Dataset(multi_X=multi_X, multi_y=multi_y_target, library=my_lib)
1✔
219
        self.assertEqual(my_dataset.n_realizations, n_realizations)
1✔
220

221
        # Test conversion to torch, when already torch tensors
222
        my_dataset = dataset.Dataset(multi_X=multi_X, multi_y=multi_y_target, library=my_lib)
1✔
223
        for i in range (n_realizations):
1✔
224
            self.assertTrue(torch.is_tensor(my_dataset.multi_X[i]))
1✔
225
            self.assertTrue(torch.is_tensor(my_dataset.multi_y[i]))
1✔
226

227
        # Test conversion to torch, when numpy arrays
228
        my_dataset = dataset.Dataset(multi_X=[X.cpu().numpy() for X in multi_X],
1✔
229
                                     multi_y=[y.cpu().numpy() for y in multi_y_target], library=my_lib)
230
        for i in range (n_realizations):
1✔
231
            self.assertTrue(torch.is_tensor(my_dataset.multi_X[i]))
1✔
232
            self.assertTrue(torch.is_tensor(my_dataset.multi_y[i]))
1✔
233

234
        # Wrong type
235
        with self.assertRaises(AssertionError):
1✔
236
            my_dataset = dataset.Dataset(multi_X=[X.cpu().numpy().astype(int) for X in multi_X], multi_y=multi_y_target,
1✔
237
                                         library=my_lib)
238
        with self.assertRaises(AssertionError):
1✔
239
            my_dataset = dataset.Dataset(multi_X=multi_X, multi_y=[y.cpu().numpy().astype(int) for y in multi_y_target],
1✔
240
                                         library=my_lib)
241
        # Containing NaNs
242
        wrong_multi_X = [X.cpu().numpy().copy() for X in multi_X]
1✔
243
        wrong_multi_X [0][0, 0] = float(np.NAN)
1✔
244
        wrong_multi_y = [y.cpu().numpy().copy() for y in multi_y_target]
1✔
245
        wrong_multi_y [0][0] = float(np.NAN)
1✔
246
        with self.assertRaises(AssertionError):
1✔
247
            my_dataset = dataset.Dataset(multi_X=wrong_multi_X, multi_y=multi_y_target, library=my_lib)
1✔
248
        with self.assertRaises(AssertionError):
1✔
249
            my_dataset = dataset.Dataset(multi_X=multi_X, multi_y=wrong_multi_y, library=my_lib)
1✔
250

251
        # Containing inconsistent n_dim
252
        wrong_multi_X = [X.cpu().numpy().copy() for X in multi_X]
1✔
253
        wrong_multi_X [0] = wrong_multi_X[0][:-1,:] # removing one dim in realization 0
1✔
254
        with self.assertRaises(AssertionError):
1✔
255
            my_dataset = dataset.Dataset(multi_X=wrong_multi_X, multi_y=multi_y_target, library=my_lib)
1✔
256

257
        # Containing too low dimension given library
258
        wrong_multi_X = [X.cpu().numpy().copy() for X in multi_X]
1✔
259
        wrong_multi_X = [np.stack([wrong_multi_X[i][0,:]]*1) for i in range(n_realizations)] # 1D per realization
1✔
260
        with self.assertRaises(AssertionError):
1✔
261
            my_dataset = dataset.Dataset(multi_X=wrong_multi_X, multi_y=multi_y_target, library=my_lib)
1✔
262

263
        # ------ Test weights as one single float ------
264
        # Creating dataset
265
        try:
1✔
266
            my_dataset = dataset.Dataset(multi_X=multi_X, multi_y=multi_y_target, multi_y_weights=2.0, library=my_lib)
1✔
267
        except:
×
268
            self.fail("Dataset creation failed.")
×
269
        # Tensor type and content
270
        for i, y_weights in enumerate(my_dataset.multi_y_weights):
1✔
271
            self.assertTrue(torch.is_tensor(y_weights))
1✔
272
            expected = torch.full_like(multi_y_target[i], fill_value=2.0)
1✔
273
            self.assertTrue((y_weights == expected).all())
1✔
274
        # NAN assertion
275
        with self.assertRaises(AssertionError):
1✔
276
            my_dataset = dataset.Dataset(multi_X=multi_X, multi_y=multi_y_target, multi_y_weights=np.NAN,
1✔
277
                                         library=my_lib)
278
        # Wrong type -> Converts to float in this case
279
        my_dataset = dataset.Dataset(multi_X=multi_X, multi_y=multi_y_target, multi_y_weights=int(2), library=my_lib)
1✔
280
        for i, y_weights in enumerate(my_dataset.multi_y_weights):
1✔
281
            self.assertTrue(torch.is_tensor(y_weights))
1✔
282
            expected = torch.full_like(multi_y_target[i], fill_value=2.0)
1✔
283
            self.assertTrue((y_weights == expected).all())
1✔
284

285
        # ------ Test weights as (n_realizations,) of floats ------
286
        # Creating dataset
287
        try:
1✔
288
            my_dataset = dataset.Dataset(multi_X=multi_X, multi_y=multi_y_target, multi_y_weights=y_weights_per_dataset,
1✔
289
                                         library=my_lib)
290
        except:
×
291
            self.fail("Dataset creation failed.")
×
292
        # Tensor type and content
293
        for i, y_weights in enumerate(my_dataset.multi_y_weights):
1✔
294
            self.assertTrue(torch.is_tensor(y_weights))
1✔
295
            expected = torch.full_like(multi_y_target[i], fill_value=y_weights_per_dataset[i])
1✔
296
            self.assertTrue((y_weights == expected).all())
1✔
297
        self.assertTrue(torch.is_tensor(my_dataset.multi_y_weights_flatten))
1✔
298
        self.assertTrue((my_dataset.multi_y_weights_flatten == y_weights_flatten ).all())
1✔
299
        # NAN assertion
300
        with self.assertRaises(AssertionError):
1✔
301
            wrong_y_weights_per_dataset = y_weights_per_dataset.copy()
1✔
302
            wrong_y_weights_per_dataset[0] = np.NAN
1✔
303
            my_dataset = dataset.Dataset(multi_X=multi_X, multi_y=multi_y_target,
1✔
304
                                         multi_y_weights=wrong_y_weights_per_dataset, library=my_lib)
305
        # Wrong (n_realizations,) length
306
        with self.assertRaises(AssertionError):
1✔
307
            wrong_y_weights_per_dataset = y_weights_per_dataset.copy()[:-1]
1✔
308
            my_dataset = dataset.Dataset(multi_X=multi_X, multi_y=multi_y_target,
1✔
309
                                         multi_y_weights=wrong_y_weights_per_dataset, library=my_lib)
310
        # Wrong type -> Converts to float in this case
311
        my_dataset = dataset.Dataset(multi_X=multi_X, multi_y=multi_y_target,
1✔
312
                                     multi_y_weights=y_weights_per_dataset.astype(int), library=my_lib)
313
        for i, y_weights in enumerate(my_dataset.multi_y_weights):
1✔
314
            self.assertTrue(torch.is_tensor(y_weights))
1✔
315
            expected = torch.full_like(multi_y_target[i], fill_value=float(int(y_weights_per_dataset[i])))
1✔
316
            self.assertTrue((y_weights == expected).all())
1✔
317

318
        # ------ Test weights as (n_realizations,) of ([n_samples depends on dataset]) ------
319
        # Creating dataset
320
        try:
1✔
321
            my_dataset = dataset.Dataset(multi_X=multi_X, multi_y=multi_y_target, multi_y_weights=multi_y_weights,
1✔
322
                                         library=my_lib)
323
        except:
×
324
            self.fail("Dataset creation failed.")
×
325
        # Tensor type and content
326
        for i, y_weights in enumerate(my_dataset.multi_y_weights):
1✔
327
            self.assertTrue(torch.is_tensor(y_weights))
1✔
328
            expected = multi_y_weights[i]
1✔
329
            self.assertTrue((y_weights == expected).all())
1✔
330
        self.assertTrue(torch.is_tensor(my_dataset.multi_y_weights_flatten))
1✔
331
        self.assertTrue((my_dataset.multi_y_weights_flatten == y_weights_flatten ).all())
1✔
332
        # NAN assertion
333
        with self.assertRaises(AssertionError):
1✔
334
            wrong_multi_y_weights = [y.cpu().numpy().copy() for y in multi_y_weights]
1✔
335
            wrong_multi_y_weights[0][0] = float(np.NAN)
1✔
336
            my_dataset = dataset.Dataset(multi_X=multi_X, multi_y=multi_y_target, multi_y_weights=wrong_multi_y_weights,
1✔
337
                                         library=my_lib)
338
        # Wrong (n_realizations,) length
339
        with self.assertRaises(AssertionError):
1✔
340
            wrong_multi_y_weights = [y.cpu().numpy().copy() for y in multi_y_weights]
1✔
341
            wrong_multi_y_weights = wrong_multi_y_weights[:-1]
1✔
342
            my_dataset = dataset.Dataset(multi_X=multi_X, multi_y=multi_y_target, multi_y_weights=wrong_multi_y_weights,
1✔
343
                                         library=my_lib)
344
        # Inconsistent n_samples
345
        with self.assertRaises(AssertionError):
1✔
346
            wrong_multi_y_weights = [y.cpu().numpy().copy() for y in multi_y_weights]
1✔
347
            wrong_multi_y_weights[0] = wrong_multi_y_weights[0][:-1]
1✔
348
            my_dataset = dataset.Dataset(multi_X=multi_X, multi_y=multi_y_target, multi_y_weights=wrong_multi_y_weights,
1✔
349
                                         library=my_lib)
350
        # Conversion to torch
351
        my_dataset = dataset.Dataset(multi_X=multi_X, multi_y=multi_y_target,
1✔
352
                                     multi_y_weights=[y.cpu().numpy() for y in multi_y_weights], library=my_lib)
353
        for i, y_weights in enumerate(my_dataset.multi_y_weights):
1✔
354
            self.assertTrue(torch.is_tensor(y_weights))
1✔
355
            expected = multi_y_weights[i]
1✔
356
            self.assertTrue((y_weights == expected).all())
1✔
357
        # Wrong type
358
        with self.assertRaises(AssertionError):
1✔
359
            my_dataset = dataset.Dataset(multi_X=multi_X, multi_y=multi_y_target,
1✔
360
                                         multi_y_weights=[y.cpu().numpy().astype(int) for y in multi_y_weights],
361
                                         library=my_lib)
362

363
        # ----- Flattened values -----
364
        my_dataset = dataset.Dataset(multi_X=multi_X, multi_y=multi_y_target, multi_y_weights=multi_y_weights,
1✔
365
                                     library=my_lib)
366
        self.assertTrue(torch.is_tensor(my_dataset.multi_X_flatten))
1✔
367
        self.assertTrue(torch.is_tensor(my_dataset.multi_y_flatten))
1✔
368
        self.assertTrue(torch.is_tensor(my_dataset.multi_y_weights_flatten))
1✔
369

370
        self.assertTrue((my_dataset.multi_X_flatten         == multi_X_flatten   ).all())
1✔
371
        self.assertTrue((my_dataset.multi_y_flatten         == y_ideals_flatten  ).all())
1✔
372
        self.assertTrue((my_dataset.multi_y_weights_flatten == y_weights_flatten ).all())
1✔
373

374

375
        return None
1✔
376

377

378

379
if __name__ == '__main__':
1✔
380
    unittest.main(verbosity=2)
×
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