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

openmrs / openmrs-core / 23193642646

17 Mar 2026 12:13PM UTC coverage: 63.1% (-0.3%) from 63.429%
23193642646

push

github

rkorytkowski
Fixing: Fix an issue with the ModuleResourceServlet

0 of 2 new or added lines in 1 file covered. (0.0%)

925 existing lines in 17 files now uncovered.

23137 of 36667 relevant lines covered (63.1%)

0.63 hits per line

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

52.12
/api/src/main/java/org/openmrs/api/context/Daemon.java
1
/**
2
 * This Source Code Form is subject to the terms of the Mozilla Public License,
3
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
4
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6
 *
7
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8
 * graphic logo is a trademark of OpenMRS Inc.
9
 */
10
package org.openmrs.api.context;
11

12
import java.util.List;
13
import java.util.concurrent.Callable;
14
import java.util.concurrent.CountDownLatch;
15
import java.util.concurrent.ExecutionException;
16
import java.util.concurrent.Future;
17
import java.util.stream.Collectors;
18

19
import org.apache.commons.collections.CollectionUtils;
20
import org.openmrs.Role;
21
import org.openmrs.User;
22
import org.openmrs.api.APIAuthenticationException;
23
import org.openmrs.api.APIException;
24
import org.openmrs.api.OpenmrsService;
25
import org.openmrs.api.db.ContextDAO;
26
import org.openmrs.api.db.hibernate.HibernateContextDAO;
27
import org.openmrs.module.DaemonToken;
28
import org.openmrs.module.Module;
29
import org.openmrs.module.ModuleException;
30
import org.openmrs.module.ModuleFactory;
31
import org.openmrs.scheduler.jobrunr.JobRequestHandlerAdapter;
32
import org.openmrs.util.OpenmrsThreadPoolHolder;
33
import org.springframework.context.support.AbstractRefreshableApplicationContext;
34

35
/**
36
 * This class allows certain tasks to run with elevated privileges. Primary use is scheduling and
37
 * module startup when there is no user to authenticate as.
38
 */
39
public final class Daemon {
40
        
41
        /**
42
         * The uuid defined for the daemon user object
43
         */
44
        static final String DAEMON_USER_UUID = "A4F30A1B-5EB9-11DF-A648-37A07F9C90FB";
45
        
46
        private static final ThreadLocal<Boolean> isDaemonThread = new ThreadLocal<>();
1✔
47
        
48
        private static final ThreadLocal<User> daemonThreadUser = new ThreadLocal<>();
1✔
49
        
50
        /**
51
         * private constructor to override the default constructor to prevent it from being instantiated.
52
         */
53
        private Daemon() {
54
        }
55
        
56
        /**
57
         * @see #startModule(Module, boolean, AbstractRefreshableApplicationContext)
58
         */
59
        public static Module startModule(Module module) throws ModuleException {
UNCOV
60
                return startModule(module, false, null);
×
61
        }
62
        
63
        /**
64
         * This method should not be called directly. The {@link ModuleFactory#startModule(Module)}
65
         * method uses this to start the given module in a new thread that is authenticated as the
66
         * daemon user. <br>
67
         * If a non-null application context is passed in, it gets refreshed to make the module's
68
         * services available
69
         *
70
         * @param module the module to start
71
         * @param isOpenmrsStartup Specifies whether this module is being started at application startup
72
         *            or not
73
         * @param applicationContext the spring application context instance to refresh
74
         * @return the module returned from {@link ModuleFactory#startModuleInternal(Module)}
75
         */
76
        public static Module startModule(final Module module, final boolean isOpenmrsStartup,
77
                final AbstractRefreshableApplicationContext applicationContext) throws ModuleException {
78
                StackTraceElement[] stack = new Exception().getStackTrace();
1✔
79
                if (stack.length < 2) {
1✔
UNCOV
80
                        throw new APIException("Could not determine where startModule() was called from");
×
81
                }
82
                StackTraceElement caller = stack[1];
1✔
83
                String callerClass = caller.getClassName();
1✔
84
                
85
                if (!Daemon.class.getName().equals(callerClass) && !ModuleFactory.class.getName().equals(callerClass)) {
1✔
UNCOV
86
                        throw new APIException("Module.factory.only", new Object[] { callerClass });
×
87
                }
88
                
89
                Future<Module> moduleStartFuture = runInDaemonThreadInternal(() -> ModuleFactory.startModuleInternal(module, isOpenmrsStartup, applicationContext));
1✔
90
                
91
                // wait for the "startModule" thread to finish
92
                try {
93
                        return moduleStartFuture.get();
1✔
94
                }
UNCOV
95
                catch (InterruptedException e) {
×
96
                        // ignore
UNCOV
97
                } catch (ExecutionException e) {
×
98
                        if (e.getCause() instanceof ModuleException) {
×
99
                                throw (ModuleException) e.getCause();
×
100
                        } else {
UNCOV
101
                                throw new ModuleException("Unable to start module " + module.getName(), e);
×
102
                        }
UNCOV
103
                }
×
104

UNCOV
105
                return module;
×
106
        }
107

108
        /**
109
         * This method should not be called directly, only {@link ContextDAO#createUser(User, String, List)} can
110
         * legally invoke this method.
111
         * 
112
         * @param user A new user to be created.
113
         * @param password The password to set for the new user.
114
         * @param roleNames A list of role names to fetch the roles to add to the user.
115
         * @return The newly created user
116
         * 
117
         * <strong>Should</strong> only allow the creation of new users, not the edition of existing ones
118
         * 
119
         * @since 2.3.0
120
         */
121
        public static User createUser(User user, String password, List<String> roleNames) throws Exception {
122
                // quick check to make sure we're only being called by ourselves
123
                StackTraceElement[] stack = new Exception().getStackTrace();
1✔
124
                if (stack.length < 2) {
1✔
UNCOV
125
                        throw new APIException("Could not determine where createUser() was called from");
×
126
                }
127
                StackTraceElement caller = stack[1];
1✔
128
                String callerClass = caller.getClassName();
1✔
129
                
130
                if (!HibernateContextDAO.class.getName().equals(callerClass)) {
1✔
131
                        throw new APIException("Context.DAO.only", new Object[] { callerClass });
1✔
132
                }
133

134
                // create a new thread and execute that task in it
135
                Future<User> userFuture = runInDaemonThreadInternal(() -> {
1✔
136
                        if ((user.getId() != null && Context.getUserService().getUser(user.getId()) != null) || Context.getUserService().getUserByUuid(user.getUuid()) != null || Context.getUserService().getUserByUsername(user.getUsername()) != null || (user.getEmail() != null && Context.getUserService().getUserByUsernameOrEmail(user.getEmail()) != null) ) {
1✔
137
                                throw new APIException("User.creating.already.exists", new Object[] { user.getDisplayString() });
1✔
138
                        }
139

140
                        if (!CollectionUtils.isEmpty(roleNames)) {
1✔
141
                                List<Role> roles = roleNames.stream().map(roleName -> Context.getUserService().getRole(roleName)).collect(Collectors.toList());
1✔
142
                                roles.forEach(user::addRole);
1✔
143
                        }
144

145
                        return Context.getUserService().createUser(user, password);
1✔
146
                });
147

148
                // wait for the 'create user' thread to finish
149
                try {
150
                        return userFuture.get();
1✔
151
                }
UNCOV
152
                catch (InterruptedException e) {
×
153
                        // ignore
154
                }
155
                catch (ExecutionException e) {
1✔
156
                        if (e.getCause() instanceof Exception) {
1✔
157
                                throw (Exception) e.getCause();
1✔
158
                        } else {
UNCOV
159
                                throw new RuntimeException(e.getCause());
×
160
                        }
UNCOV
161
                }
×
162

UNCOV
163
                return null;
×
164
        }        
165
        
166
        /**
167
         * Call this method if you are inside a Daemon thread (for example in a Module activator or a
168
         * scheduled task) and you want to start up a new parallel Daemon thread. You may only call this
169
         * method from a Daemon thread.
170
         *
171
         * @param runnable what to run in a new thread
172
         * @return the newly spawned {@link Thread}
173
         * @deprecated As of 2.7.0, consider using {@link #runNewDaemonTask(Runnable)} instead
174
         */
175
        @Deprecated
176
        public static Thread runInNewDaemonThread(final Runnable runnable) {
177
                // make sure we're already in a daemon thread
178
                if (!isDaemonThread()) {
1✔
179
                        throw new APIAuthenticationException("Only daemon threads can spawn new daemon threads");
1✔
180
                }
181

182
                // the previous implementation ensured that Thread.start() was called before this function returned
183
                // since we cannot guarantee that the executor will run the thread when `execute()` is called, we need another
184
                // mechanism to ensure the submitted Runnable was actually started.
UNCOV
185
                final CountDownLatch countDownLatch = new CountDownLatch(1);
×
186
                
187
                // we should consider making DaemonThread public, so the caller can access returnedObject and exceptionThrown
188
                DaemonThread thread = new DaemonThread() {
×
189
                        
190
                        @Override
191
                        public void run() {
UNCOV
192
                                isDaemonThread.set(true);
×
193
                                try {
UNCOV
194
                                        Context.openSession();
×
UNCOV
195
                                        countDownLatch.countDown();
×
196
                                        //Suppressing sonar issue "squid:S1217"
197
                                        //We intentionally do not start a new thread yet, rather wrap the run call in a session.
UNCOV
198
                                        runnable.run();
×
199
                                }
200
                                finally {
201
                                        try {
202
                                                Context.closeSession();
×
203
                                        } finally {
204
                                                isDaemonThread.remove();
×
205
                                                daemonThreadUser.remove();
×
206
                                        }
207
                                }
208
                        }
×
209
                };
210
                
UNCOV
211
                OpenmrsThreadPoolHolder.threadExecutor.execute(thread);
×
212

213
                // do not return until the thread is actually started to emulate the previous behaviour
214
                try {
UNCOV
215
                        countDownLatch.await();
×
UNCOV
216
                } catch (InterruptedException ignored) {
×
UNCOV
217
                }
×
218

UNCOV
219
                return thread;
×
220
        }
221

222
        /**
223
         * Call this method if you are inside a Daemon thread (for example in a Module activator or a
224
         * scheduled task) and you want to start up a new parallel Daemon thread. You may only call this
225
         * method from a Daemon thread.
226
         *
227
         * @param callable what to run in a new thread
228
         * @return a future that completes when the task is done;
229
         * @since 2.7.0
230
         */
231
        @SuppressWarnings({"squid:S1217", "unused"})
232
        public static <T> Future<T> runInNewDaemonThread(final Callable<T> callable) {
233
                // make sure we're already in a daemon thread
234
                if (!isDaemonThread()) {
1✔
235
                        throw new APIAuthenticationException("Only daemon threads can spawn new daemon threads");
1✔
236
                }
237

UNCOV
238
                return runInDaemonThreadInternal(callable);
×
239
        }
240

241
        /**
242
         * Call this method if you are inside a Daemon thread (for example in a Module activator or a
243
         * scheduled task) and you want to start up a new parallel Daemon thread. You may only call this
244
         * method from a Daemon thread.
245
         *
246
         * @param runnable what to run in a new thread
247
         * @return a future that completes when the task is done;
248
         * @since 2.7.0
249
         */
250
        @SuppressWarnings({"squid:S1217", "unused"})
251
        public static Future<?> runNewDaemonTask(final Runnable runnable) {
252
                // make sure we're already in a daemon thread
253
                if (!isDaemonThread()) {
1✔
254
                        throw new APIAuthenticationException("Only daemon threads can spawn new daemon threads");
1✔
255
                }
256
                
UNCOV
257
                return runInDaemonThreadInternal(runnable);
×
258
        }
259
        
260
        /**
261
         * @return true if the current thread was started by this class and so is a daemon thread that
262
         *         has all privileges
263
         * @see Context#hasPrivilege(String)
264
         */
265
        public static boolean isDaemonThread() {
266
                Boolean b = isDaemonThread.get();
1✔
267
                if (b == null || !b) {
1✔
268
                        // Allow functions in Daemon and WebDaemon to be treated as a DaemonThread
269
                        StackTraceElement[] stack = new Exception().getStackTrace();
1✔
270
                        if (stack.length < 3) {
1✔
UNCOV
271
                                throw new APIException("Could not determine where change password was called from");
×
272
                        }
273
                        StackTraceElement caller = stack[2];
1✔
274
                        String callerClass = caller.getClassName();
1✔
275
                        
276
                        return Daemon.class.getName().equals(callerClass) || "org.openmrs.web.WebDaemon".equals(callerClass);
1✔
277
                } else {
278
                        return true;
1✔
279
                }
280
        }
281
        
282
        /**
283
         * Calls the {@link OpenmrsService#onStartup()} method, as a daemon, for an instance
284
         * implementing the {@link OpenmrsService} interface.
285
         *
286
         * @param service instance implementing the {@link OpenmrsService} interface.
287
         * @since 1.9
288
         */
289
        public static void runStartupForService(final OpenmrsService service) throws ModuleException {
290
                StackTraceElement[] stack = new Exception().getStackTrace();
1✔
291
                if (stack.length < 2) {
1✔
UNCOV
292
                        throw new APIException("Could not determine where change password was called from");
×
293
                }
294
                StackTraceElement caller = stack[1];
1✔
295
                String callerClass = caller.getClassName();
1✔
296
                
297
                if (!ServiceContext.class.getName().equals(callerClass)) {
1✔
UNCOV
298
                        throw new APIException("Service.context.only", new Object[] { callerClass });
×
299
                }
300
                
301
                Future<?> future = runInDaemonThreadInternal(service::onStartup);
1✔
302
                
303
                // wait for the "onStartup" thread to finish
304
                try {
305
                        future.get();
1✔
306
                }
UNCOV
307
                catch (InterruptedException e) {
×
308
                        // ignore
309
                }
UNCOV
310
                catch (ExecutionException e) {
×
UNCOV
311
                        if (e.getCause() instanceof ModuleException) {
×
UNCOV
312
                                throw (ModuleException) e.getCause();
×
313
                        } else {
UNCOV
314
                                throw new ModuleException("Unable to run onStartup() method of service {}", service.getClass().getSimpleName(), e);
×
315
                        }
316
                }
1✔
317
        }
1✔
318
        
319
        /**
320
         * Executes the given runnable in a new thread that is authenticated as the daemon user.
321
         *
322
         * @param runnable an object implementing the {@link Runnable} interface.
323
         * @param token the token required to run code as the daemon user
324
         * @return the newly spawned {@link Thread}
325
         * @since 1.9.2
326
         * @deprecated Since 2.7.0 use {@link #runInDaemonThreadWithoutResult(Runnable, DaemonToken)} instead
327
         */
328
        @Deprecated
329
        @SuppressWarnings({"squid:S1217", "unused"})
330
        public static Thread runInDaemonThread(final Runnable runnable, DaemonToken token) {
UNCOV
331
                if (!ModuleFactory.isTokenValid(token)) {
×
UNCOV
332
                        throw new ContextAuthenticationException("Invalid token " + token);
×
333
                }
334
                
UNCOV
335
                DaemonThread thread = new DaemonThread() {
×
336
                        @Override
337
                        public void run() {
UNCOV
338
                                isDaemonThread.set(true);
×
339
                                try {
UNCOV
340
                                        Context.openSession();
×
341
                                        //Suppressing sonar issue "squid:S1217"
342
                                        //We intentionally do not start a new thread yet, rather wrap the run call in a session.
UNCOV
343
                                        runnable.run();
×
344
                                }
345
                                finally {
346
                                        try {
UNCOV
347
                                                Context.closeSession();
×
348
                                        } finally {
UNCOV
349
                                                isDaemonThread.remove();
×
UNCOV
350
                                                daemonThreadUser.remove();
×
351
                                        }
352
                                }
UNCOV
353
                        }
×
354
                };
355

UNCOV
356
                OpenmrsThreadPoolHolder.threadExecutor.execute(thread);
×
357
                return thread;
×
358
        }
359

360
        /**
361
         * Executes the given runnable in a new thread that is authenticated as the daemon user.
362
         *
363
         * @param callable an object implementing the {@link Callable<T>} interface to be run
364
         * @param token the token required to run code as the daemon user
365
         * @return the newly spawned {@link Thread}
366
         * @since 2.7.0
367
         */
368
        @SuppressWarnings({"squid:S1217", "unused"})
369
        public static <T> Future<T> runInDaemonThread(final Callable<T> callable, DaemonToken token) {
UNCOV
370
                if (!ModuleFactory.isTokenValid(token)) {
×
UNCOV
371
                        throw new ContextAuthenticationException("Invalid token");
×
372
                }
373
                
UNCOV
374
                return runInDaemonThreadInternal(callable);
×
375
        }
376

377
        /**
378
         * Executes the given runnable in a new thread that is authenticated as the daemon user.
379
         *
380
         * @param runnable an object implementing the {@link Runnable} interface to be run
381
         * @param token the token required to run code as the daemon user
382
         * @return the newly spawned {@link Thread}
383
         * @since 2.7.0
384
         */
385
        @SuppressWarnings("squid:S1217")
386
        public static Future<?> runInDaemonThreadWithoutResult(final Runnable runnable, DaemonToken token) {
387
                if (!ModuleFactory.isTokenValid(token)) {
×
UNCOV
388
                        throw new ContextAuthenticationException("Invalid token");
×
389
                }
390

UNCOV
391
                return runInDaemonThreadInternal(runnable);
×
392
        }
393
        
394
        /**
395
         * Executes the given runnable in a new thread that is authenticated as the daemon user and wait
396
         * for the thread to finish.
397
         *
398
         * @param runnable an object implementing the {@link Runnable} interface.
399
         * @param token the token required to run code as the daemon user
400
         * @since 2.7.0
401
         */
402
        public static void runInDaemonThreadAndWait(final Runnable runnable, DaemonToken token) {
403
                Future<?> daemonThread = runInDaemonThreadWithoutResult(runnable, token);
×
404
                
405
                try {
UNCOV
406
                        daemonThread.get();
×
407
                }
UNCOV
408
                catch (InterruptedException | ExecutionException e) {
×
409
                        // Ignored
UNCOV
410
                }
×
UNCOV
411
        }
×
412
        
413
        private static <T> Future<T> runInDaemonThreadInternal(Callable<T> callable) {
414
                return OpenmrsThreadPoolHolder.threadExecutor.submit(() -> {
1✔
415
                        isDaemonThread.set(true);
1✔
416
                        try {
417
                                Context.openSession();
1✔
418
                                return callable.call();
1✔
419
                        }
420
                        finally {
421
                                try {
422
                                        Context.closeSession();
1✔
423
                                } finally {
424
                                        isDaemonThread.remove();
1✔
425
                                        daemonThreadUser.remove();
1✔
426
                                }
427
                        }
428
                });
429
        }
430
        
431
        private static Future<?> runInDaemonThreadInternal(Runnable runnable) {
432
                // for Threads, we used to guarantee that Thread.start() was called before the function returned
433
                // since we cannot guarantee that the executor actually started executing the thread, we use a CountDownLatch
434
                // to emulate this behaviour when the user submits a Thread. Other runnables are unaffected.
435
                CountDownLatch countDownLatch = getCountDownLatch(runnable instanceof Thread);
1✔
436

437
                Future<?> result = OpenmrsThreadPoolHolder.threadExecutor.submit(() -> {
1✔
438
                        isDaemonThread.set(true);
1✔
439
                        try {
440
                                Context.openSession();
1✔
441
                                countDownLatch.countDown();
1✔
442
                                runnable.run();
1✔
443
                        }
444
                        finally {
445
                                try {
446
                                        Context.closeSession();
1✔
447
                                } finally {
448
                                        isDaemonThread.remove();
1✔
449
                                        daemonThreadUser.remove();
1✔
450
                                }
451
                        }
452
                });
1✔
453
                
454
                try {
455
                        countDownLatch.await();
1✔
456
                } catch (InterruptedException ignored) {}
1✔
457
                
458
                return result;
1✔
459
        }
460

461
        private static CountDownLatch getCountDownLatch(boolean isThread) {
462
                return isThread ? new CountDownLatch(1) : new CountDownLatch(0);
1✔
463
        }
464

465
        /**
466
         * Executes the given task as the given user. <br>
467
         * <br>
468
         * This can only be called from {@link JobRequestHandlerAdapter} during actual task execution
469
         * <p>
470
         * <strong>Should</strong> not be called from other methods other than JobRequestHandlerAdapter
471
         * <strong>Should</strong> not throw error if called from a JobRequestHandlerAdapter class
472
         *          
473
         * @param userSystemId the user to run as
474
         * @param runnable the task to run
475
         *
476
         * @since 2.9.x
477
         */
478
        public static void executeScheduledTaskAsUser(String userSystemId, DaemonTask runnable) throws Exception {
479
                // quick check to make sure we're only being called by ourselves
480
                StackTraceElement[] stack = new Exception().getStackTrace();
1✔
481
                if (stack.length < 2) {
1✔
UNCOV
482
                        throw new APIException("Could not determine where executeScheduledTaskAsUser was called from");
×
483
                }
484
                StackTraceElement caller = stack[1];
1✔
485
                String callerClass = caller.getClassName();
1✔
486

487
                Class<?> callerClazz;
488
                try {
489
                        callerClazz = Class.forName(callerClass);
1✔
UNCOV
490
                } catch (ClassNotFoundException e) {
×
UNCOV
491
                        throw new APIException("Could not determine where executeScheduledTaskAsUser was called from", e);
×
492
                }
1✔
493

494
                if (!JobRequestHandlerAdapter.class.isAssignableFrom(callerClazz)) {
1✔
495
                        throw new APIException("executeScheduledTaskAsUser can only be called from JobRequestHandlerAdapter", new Object[] { callerClass });
1✔
496
                }
497
                
UNCOV
498
                isDaemonThread.set(true);
×
499
                try {
UNCOV
500
                        Context.openSession();
×
UNCOV
501
                        Context.getUserContext().becomeUser(userSystemId);
×
UNCOV
502
                        isDaemonThread.remove();
×
UNCOV
503
                        runnable.run();
×
504
                }
505
                finally {
UNCOV
506
                        isDaemonThread.remove();
×
UNCOV
507
                        Context.closeSession();
×
508
                }
UNCOV
509
        }
×
510

511
        /**
512
         * Thread class used by the {@link Daemon#startModule(Module)} method so that the returned object and the
513
         * exception thrown can be returned to calling class
514
         */
UNCOV
515
        protected static class DaemonThread extends Thread {
×
516
                
517
                /**
518
                 * The object returned from the method called in {@link #run()}
519
                 */
UNCOV
520
                protected Object returnedObject = null;
×
521
                
522
                /**
523
                 * The exception thrown (if any) by the method called in {@link #run()}
524
                 */
UNCOV
525
                protected Exception exceptionThrown = null;
×
526
                
527
                /**
528
                 * Gets the exception thrown (if any) by the method called in {@link #run()}
529
                 *
530
                 * @return the thrown exception (if any).
531
                 */
532
                public Exception getExceptionThrown() {
UNCOV
533
                        return exceptionThrown;
×
534
                }
535
        }
536

537
        @FunctionalInterface
538
        public interface DaemonTask {
539

540
                void run() throws Exception;
541
        }
542
        
543
        /**
544
         * Checks whether user is Daemon.
545
         * However, this is not the preferred method for checking to see if the current thread is a daemon thread,
546
         *                                 rather use {@link #isDaemonThread()}.
547
         * isDaemonThread is preferred for checking to see if you are in that thread or if the current thread is daemon.
548
         *
549
         * @param user user whom we are checking if daemon
550
         * @return true if user is Daemon
551
         */
552
        public static boolean isDaemonUser(User user) {
553
                return DAEMON_USER_UUID.equals(user.getUuid());
1✔
554
        }
555
        
556
        /**
557
         * @return the current thread daemon user or null if not assigned
558
         * @since 2.0.0, 1.12.0, 1.11.6, 1.10.4, 1.9.11
559
         */
560
        public static User getDaemonThreadUser() {
561
                if (isDaemonThread()) {
1✔
562
                        User user = daemonThreadUser.get();
1✔
563
                        if (user == null) {
1✔
564
                                user = Context.getContextDAO().getUserByUuid(DAEMON_USER_UUID);
1✔
565
                                daemonThreadUser.set(user);
1✔
566
                        }
567
                        return user;
1✔
568
                } else {
UNCOV
569
                        return null;
×
570
                }
571
        }
572

573
        public static String getDaemonUserUuid() {
574
                return DAEMON_USER_UUID;
1✔
575
        }
576
}
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