Skip to content

Commit cf1fde8

Browse files
bigmontzthelonelyvulpesrobsdedude
authored
Fix flaky tests on TransactionExecutor suite (#1137)
The TransactionExecutor tests were mocking the global setTimeout and clearTimeout. This mocks were conflicting with other calls to this api in the browser tests. So, the calls to the set/clear were not being the expected in some situations. Replace the global mock for injecting the functions resolve this issue in a less intrusive way. Co-authored-by: grant lodge <6323995+thelonelyvulpes@users.noreply.github.com> Co-authored-by: Robsdedude <dev@rouvenbauer.de>
1 parent ee4baf7 commit cf1fde8

File tree

4 files changed

+237
-211
lines changed

4 files changed

+237
-211
lines changed

packages/core/src/internal/transaction-executor.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,41 @@ type TransactionWork<T, Tx = Transaction> = (tx: Tx) => T | Promise<T>
3232
type Resolve<T> = (value: T | PromiseLike<T>) => void
3333
type Reject = (value: any) => void
3434
type Timeout = ReturnType<typeof setTimeout>
35+
type SetTimeout = (callback: (...args: unknown[]) => void, ms: number | undefined, ...args: unknown[]) => Timeout
36+
type ClearTimeout = (timeoutId: Timeout) => void
37+
38+
interface Dependencies {
39+
setTimeout: SetTimeout
40+
clearTimeout: ClearTimeout
41+
}
42+
43+
function setTimeoutWrapper (callback: (...args: unknown[]) => void, ms: number | undefined, ...args: unknown[]): Timeout {
44+
return setTimeout(callback, ms, ...args)
45+
}
46+
47+
function clearTimeoutWrapper (timeoutId: Timeout): void {
48+
return clearTimeout(timeoutId)
49+
}
3550

3651
export class TransactionExecutor {
3752
private readonly _maxRetryTimeMs: number
3853
private readonly _initialRetryDelayMs: number
3954
private readonly _multiplier: number
4055
private readonly _jitterFactor: number
4156
private _inFlightTimeoutIds: Timeout[]
57+
private readonly _setTimeout: SetTimeout
58+
private readonly _clearTimeout: ClearTimeout
4259
public pipelineBegin: boolean
4360

4461
constructor (
4562
maxRetryTimeMs?: number | null,
4663
initialRetryDelayMs?: number,
4764
multiplier?: number,
48-
jitterFactor?: number
65+
jitterFactor?: number,
66+
dependencies: Dependencies = {
67+
setTimeout: setTimeoutWrapper,
68+
clearTimeout: clearTimeoutWrapper
69+
}
4970
) {
5071
this._maxRetryTimeMs = _valueOrDefault(
5172
maxRetryTimeMs,
@@ -64,6 +85,9 @@ export class TransactionExecutor {
6485
DEFAULT_RETRY_DELAY_JITTER_FACTOR
6586
)
6687

88+
this._setTimeout = dependencies.setTimeout
89+
this._clearTimeout = dependencies.clearTimeout
90+
6791
this._inFlightTimeoutIds = []
6892
this.pipelineBegin = false
6993

@@ -99,7 +123,7 @@ export class TransactionExecutor {
99123

100124
close (): void {
101125
// cancel all existing timeouts to prevent further retries
102-
this._inFlightTimeoutIds.forEach(timeoutId => clearTimeout(timeoutId))
126+
this._inFlightTimeoutIds.forEach(timeoutId => this._clearTimeout(timeoutId))
103127
this._inFlightTimeoutIds = []
104128
}
105129

@@ -119,7 +143,7 @@ export class TransactionExecutor {
119143

120144
return new Promise<T>((resolve, reject) => {
121145
const nextRetryTime = this._computeDelayWithJitter(retryDelayMs)
122-
const timeoutId = setTimeout(() => {
146+
const timeoutId = this._setTimeout(() => {
123147
// filter out this timeoutId when time has come and function is being executed
124148
this._inFlightTimeoutIds = this._inFlightTimeoutIds.filter(
125149
id => id !== timeoutId

packages/neo4j-driver-deno/lib/core/internal/transaction-executor.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,41 @@ type TransactionWork<T, Tx = Transaction> = (tx: Tx) => T | Promise<T>
3232
type Resolve<T> = (value: T | PromiseLike<T>) => void
3333
type Reject = (value: any) => void
3434
type Timeout = ReturnType<typeof setTimeout>
35+
type SetTimeout = (callback: (...args: unknown[]) => void, ms: number | undefined, ...args: unknown[]) => Timeout
36+
type ClearTimeout = (timeoutId: Timeout) => void
37+
38+
interface Dependencies {
39+
setTimeout: SetTimeout
40+
clearTimeout: ClearTimeout
41+
}
42+
43+
function setTimeoutWrapper (callback: (...args: unknown[]) => void, ms: number | undefined, ...args: unknown[]): Timeout {
44+
return setTimeout(callback, ms, ...args)
45+
}
46+
47+
function clearTimeoutWrapper (timeoutId: Timeout): void {
48+
return clearTimeout(timeoutId)
49+
}
3550

3651
export class TransactionExecutor {
3752
private readonly _maxRetryTimeMs: number
3853
private readonly _initialRetryDelayMs: number
3954
private readonly _multiplier: number
4055
private readonly _jitterFactor: number
4156
private _inFlightTimeoutIds: Timeout[]
57+
private readonly _setTimeout: SetTimeout
58+
private readonly _clearTimeout: ClearTimeout
4259
public pipelineBegin: boolean
4360

4461
constructor (
4562
maxRetryTimeMs?: number | null,
4663
initialRetryDelayMs?: number,
4764
multiplier?: number,
48-
jitterFactor?: number
65+
jitterFactor?: number,
66+
dependencies: Dependencies = {
67+
setTimeout: setTimeoutWrapper,
68+
clearTimeout: clearTimeoutWrapper
69+
}
4970
) {
5071
this._maxRetryTimeMs = _valueOrDefault(
5172
maxRetryTimeMs,
@@ -64,6 +85,9 @@ export class TransactionExecutor {
6485
DEFAULT_RETRY_DELAY_JITTER_FACTOR
6586
)
6687

88+
this._setTimeout = dependencies.setTimeout
89+
this._clearTimeout = dependencies.clearTimeout
90+
6791
this._inFlightTimeoutIds = []
6892
this.pipelineBegin = false
6993

@@ -99,7 +123,7 @@ export class TransactionExecutor {
99123

100124
close (): void {
101125
// cancel all existing timeouts to prevent further retries
102-
this._inFlightTimeoutIds.forEach(timeoutId => clearTimeout(timeoutId))
126+
this._inFlightTimeoutIds.forEach(timeoutId => this._clearTimeout(timeoutId))
103127
this._inFlightTimeoutIds = []
104128
}
105129

@@ -119,7 +143,7 @@ export class TransactionExecutor {
119143

120144
return new Promise<T>((resolve, reject) => {
121145
const nextRetryTime = this._computeDelayWithJitter(retryDelayMs)
122-
const timeoutId = setTimeout(() => {
146+
const timeoutId = this._setTimeout(() => {
123147
// filter out this timeoutId when time has come and function is being executed
124148
this._inFlightTimeoutIds = this._inFlightTimeoutIds.filter(
125149
id => id !== timeoutId

packages/neo4j-driver/test/internal/timers-util.js

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,53 +15,46 @@
1515
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1616
* See the License for the specific language governing permissions and
1717
* limitations under the License.
18+
*/
19+
20+
/**
21+
* This is a lighter mock which only creates mocked functions to work
22+
* as timeouts.
1823
*/
19-
class SetTimeoutMock {
24+
class TimeoutsMock {
2025
constructor () {
21-
this._clearState()
26+
this.clearState()
27+
// bind it to be used as standalone functions
28+
this.setTimeout = this.setTimeout.bind(this)
29+
this.clearTimeout = this.clearTimeout.bind(this)
2230
}
2331

24-
install () {
25-
this._originalSetTimeout = global.setTimeout
26-
global.setTimeout = (code, delay) => {
27-
if (!this._paused) {
28-
code()
29-
this.invocationDelays.push(delay)
30-
}
31-
return this._timeoutIdCounter++
32-
}
33-
34-
this._originalClearTimeout = global.clearTimeout
35-
global.clearTimeout = id => {
36-
this.clearedTimeouts.push(id)
32+
setTimeout (code, delay) {
33+
const timeoutId = this._timeoutIdCounter++
34+
this.invocationDelays.push(delay)
35+
if (!this._timeoutCallbacksDisabled) {
36+
code()
3737
}
38-
39-
return this
40-
}
41-
42-
pause () {
43-
this._paused = true
38+
return timeoutId
4439
}
4540

46-
uninstall () {
47-
global.setTimeout = this._originalSetTimeout
48-
global.clearTimeout = this._originalClearTimeout
49-
this._clearState()
41+
clearTimeout (id) {
42+
this.clearedTimeouts.push(id)
5043
}
5144

52-
setTimeoutOriginal (code, delay) {
53-
return this._originalSetTimeout.call(null, code, delay)
45+
disableTimeoutCallbacks () {
46+
this._timeoutCallbacksDisabled = true
5447
}
5548

56-
_clearState () {
57-
this._originalSetTimeout = null
58-
this._originalClearTimeout = null
59-
this._paused = false
49+
clearState () {
50+
this._timeoutCallbacksDisabled = false
6051
this._timeoutIdCounter = 0
6152

6253
this.invocationDelays = []
6354
this.clearedTimeouts = []
6455
}
6556
}
6657

67-
export const setTimeoutMock = new SetTimeoutMock()
58+
export {
59+
TimeoutsMock
60+
}

0 commit comments

Comments
 (0)