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

rokucommunity / brighterscript / #13235

28 Oct 2024 07:06PM UTC coverage: 89.066%. Remained the same
#13235

push

web-flow
Merge 9bcb77aad into 9ec6f722c

7233 of 8558 branches covered (84.52%)

Branch coverage included in aggregate %.

34 of 34 new or added lines in 5 files covered. (100.0%)

46 existing lines in 4 files now uncovered.

9621 of 10365 relevant lines covered (92.82%)

1782.28 hits per line

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

97.56
/src/BusyStatusTracker.ts
1
import { EventEmitter } from 'eventemitter3';
1✔
2
import { Deferred } from './deferred';
1✔
3

4
interface RunInfo<T> {
5
    label: string;
6
    startTime: Date;
7
    scope?: T;
8
    deferred?: Deferred;
9
}
10

11
/**
12
 * Tracks the busy/idle status of various sync or async tasks
13
 * Reports the overall status to the client
14
 */
15
export class BusyStatusTracker<T = any> {
1✔
16
    /**
17
     * @readonly
18
     */
19
    public activeRuns: Array<RunInfo<T>> = [];
152✔
20

21
    /**
22
     * Begin a busy task. It's expected you will call `endScopeRun` when the task is complete.
23
     * @param scope an object used for reference as to what is doing the work. Can be used to bulk-cancel all runs for a given scope.
24
     * @param label  label for the run. This is required for the `endScopedRun` method to know what to end.
25
     */
26
    public beginScopedRun(scope: T, label: string) {
27
        const deferred = new Deferred();
91✔
28

29
        const runInfo = {
91✔
30
            label: label,
31
            startTime: new Date(),
32
            deferred: deferred,
33
            scope: scope
34
        };
35

36
        void this._run(runInfo, () => {
91✔
37
            //don't mark the busy run as completed until the deferred is resolved
38
            return deferred.promise;
91✔
39
        });
40
    }
41

42
    /**
43
     * End the earliest run for the given scope and label
44
     * @param scope an object used for reference as to what is doing the work. Can be used to bulk-cancel all runs for a given scope.
45
     * @param label label for the run
46
     */
47
    public endScopedRun(scope: T, label: string) {
48
        const earliestRunIndex = this.activeRuns.findIndex(x => x.scope === scope && x.label === label);
119✔
49
        if (earliestRunIndex === -1) {
85!
UNCOV
50
            return;
×
51
        }
52
        const earliestRun = this.activeRuns[earliestRunIndex];
85✔
53
        this.activeRuns.splice(earliestRunIndex, 1);
85✔
54
        earliestRun.deferred.resolve();
85✔
55
        return earliestRun.deferred.promise;
85✔
56
    }
57

58
    /**
59
     * End all runs for a given scope. This is typically used when the scope is destroyed, and we want to make sure all runs are cleaned up.
60
     * @param scope an object used for reference as to what is doing the work.
61
     */
62
    public endAllRunsForScope(scope: T) {
63
        for (let i = this.activeRuns.length - 1; i >= 0; i--) {
13✔
64
            const activeRun = this.activeRuns[i];
9✔
65
            if (activeRun.scope === scope) {
9✔
66
                //delete the active run
67
                this.activeRuns.splice(i, 1);
3✔
68
                //mark the run as resolved
69
                activeRun.deferred.resolve();
3✔
70
            }
71
        }
72
    }
73

74
    /**
75
     * Start a new piece of work
76
     */
77
    public run<A, R = A | Promise<A>>(callback: (finalize?: FinalizeBuildStatusRun) => R, label?: string): R {
78
        const run = {
277✔
79
            label: label,
80
            startTime: new Date()
81
        };
82
        return this._run<A, R>(run, callback);
277✔
83
    }
84

85
    private _run<A, R = A | Promise<A>>(runInfo: RunInfo<T>, callback: (finalize?: FinalizeBuildStatusRun) => R, label?: string): R {
86
        this.activeRuns.push(runInfo);
368✔
87

88
        if (this.activeRuns.length === 1) {
368✔
89
            this.emit('change', BusyStatus.busy);
209✔
90
        }
91
        this.emit('active-runs-change', { activeRuns: [...this.activeRuns] });
368✔
92

93
        let isFinalized = false;
368✔
94
        const finalizeRun = () => {
368✔
95
            if (isFinalized === false) {
367✔
96
                isFinalized = true;
365✔
97

98
                this.emit('active-runs-change', { activeRuns: [...this.activeRuns] });
365✔
99

100
                let idx = this.activeRuns.indexOf(runInfo);
365✔
101
                if (idx > -1) {
365✔
102
                    this.activeRuns.splice(idx, 1);
277✔
103
                }
104
                if (this.activeRuns.length <= 0) {
365✔
105
                    this.emit('change', BusyStatus.idle);
209✔
106
                }
107
            }
108
        };
109

110
        let result: R | PromiseLike<R>;
111
        //call the callback function
112
        try {
368✔
113
            result = callback(finalizeRun);
368✔
114
            //if the result is a promise, don't finalize until it completes
115
            if (typeof (result as any)?.then === 'function') {
367✔
116
                return Promise.resolve(result).finally(finalizeRun).then(() => result) as any;
356✔
117
            } else {
118
                finalizeRun();
11✔
119
                return result;
11✔
120
            }
121
        } catch (e) {
122
            finalizeRun();
1✔
123
            throw e;
1✔
124
        }
125
    }
126

127
    private emitter = new EventEmitter<string, BusyStatus>();
152✔
128

129
    public once(eventName: 'active-runs-change'): Promise<{ activeRuns: Array<RunInfo<T>> }>;
130
    public once(eventName: 'change'): Promise<BusyStatus>;
131
    public once<T>(eventName: string): Promise<T> {
132
        return new Promise<T>((resolve) => {
4✔
133
            const off = this.on(eventName as any, (data) => {
4✔
134
                off();
4✔
135
                resolve(data as any);
4✔
136
            });
137
        });
138
    }
139

140
    public on(eventName: 'active-runs-change', handler: (event: { activeRuns: Array<RunInfo<T>> }) => void);
141
    public on(eventName: 'change', handler: (status: BusyStatus) => void);
142
    public on(eventName: string, handler: (event: any) => void) {
143
        this.emitter.on(eventName, handler);
74✔
144
        return () => {
74✔
145
            this.emitter.off(eventName, handler);
5✔
146
        };
147
    }
148

149
    private emit(eventName: 'active-runs-change', event: { activeRuns: Array<RunInfo<T>> });
150
    private emit(eventName: 'change', status: BusyStatus);
151
    private emit(eventName: string, event: any) {
152
        this.emitter.emit(eventName, event);
1,151✔
153
    }
154

155
    public destroy() {
156
        this.emitter.removeAllListeners();
14✔
157
    }
158

159
    /**
160
     * The current status of the busy tracker.
161
     * @readonly
162
     */
163
    public get status() {
164
        return this.activeRuns.length === 0 ? BusyStatus.idle : BusyStatus.busy;
404✔
165
    }
166
}
167

168
export type FinalizeBuildStatusRun = (status?: BusyStatus) => void;
169

170
export enum BusyStatus {
1✔
171
    busy = 'busy',
1✔
172
    idle = 'idle'
1✔
173
}
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