Skip to content

Commit

Permalink
wrap dark with a function, move getTestNamePath there
Browse files Browse the repository at this point in the history
  • Loading branch information
ChALkeR committed Jul 13, 2024
1 parent c331e0a commit 9454907
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 81 deletions.
110 changes: 86 additions & 24 deletions src/dark.cjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const { fileURLToPath } = require('node:url')

const mayBeUrlToPath = (str) => (str.startsWith('file://') ? fileURLToPath(str) : str)

let locForNextTest

let installLocationInNextTest = function (loc) {
Expand All @@ -9,9 +11,7 @@ let installLocationInNextTest = function (loc) {
// WARNING
// Do not refactor, do not wrap
// This function has to be called unwrapped directly inside our test() impl
let getCallerLocation = () => {}

const mayBeUrlToPath = (str) => (str.startsWith('file://') ? fileURLToPath(str) : str)
let getCallerLocation

// This is unoptimal
// Ideally, an option for overriding file locations should be added to Node.js,
Expand All @@ -21,28 +21,90 @@ const mayBeUrlToPath = (str) => (str.startsWith('file://') ? fileURLToPath(str)
// This whole logic is limited only to updating caller locations for reports
// We don't do use exposed Node.js internas for anything else

try {
const { Test } = require('node:internal/test_runner/test')
const locStorage = new Map()
Object.defineProperty(Test.prototype, 'loc', {
get() {
return locStorage.get(this)
},
set(val) {
locStorage.set(this, val)
if (locForNextTest) {
const loc = locForNextTest
locForNextTest = undefined
locStorage.set(this, { line: loc[0], column: loc[1], file: mayBeUrlToPath(loc[2]) })
function createCallerLocationHook() {
if (getCallerLocation) return { installLocationInNextTest, getCallerLocation }

try {
const { Test } = require('node:internal/test_runner/test')
const locStorage = new Map()
Object.defineProperty(Test.prototype, 'loc', {
get() {
return locStorage.get(this)
},
set(val) {
locStorage.set(this, val)
if (locForNextTest) {
const loc = locForNextTest
locForNextTest = undefined
locStorage.set(this, { line: loc[0], column: loc[1], file: mayBeUrlToPath(loc[2]) })
}
},
})

// We can replicate getCallerLocation() with public V8 Error CallSite API, but we won't
// need it anyway if we don't have a path for hook into internal Test implementation

const { internalBinding } = require('node:internal/test/binding')
getCallerLocation = internalBinding('util').getCallerLocation
} catch {
getCallerLocation = () => {}
}

return { installLocationInNextTest, getCallerLocation }
}

// Easy on Node.js >= 22.3.0, but we polyfill for the rest
function getTestNamePath(t) {
// No implementation in Node.js yet, will have to PR
if (t.fullName) return t.fullName.split(' > ')

// We are on Node.js < 22.3.0 where even t.fullName doesn't exist yet, polyfill
const namePath = Symbol('namePath')
const getNamePath = Symbol('getNamePath')
try {
if (t[namePath]) return t[namePath]

// Sigh, ok, whatever
const { Test } = require('node:internal/test_runner/test')

const usePathName = Symbol('usePathName')
const restoreName = Symbol('restoreName')
Test.prototype[getNamePath] = function () {
if (this === this.root) return []
return [...(this.parent?.[getNamePath]() || []), this.name]
}

const diagnostic = Test.prototype.diagnostic
Test.prototype.diagnostic = function (...args) {
if (args[0] === usePathName) {
this[restoreName] = this.name
this.name = this[getNamePath]()
return
}
},
})

// We can replicate getCallerLocation() with public V8 Error CallSite API, but we won't
// need it anyway if we don't have a path for hook into internal Test implementation
if (args[0] === restoreName) {
this.name = this[restoreName]
delete this[restoreName]
return
}

const { internalBinding } = require('node:internal/test/binding')
getCallerLocation = internalBinding('util').getCallerLocation
} catch {}
return diagnostic.apply(this, args)
}

const TestContextProto = Object.getPrototypeOf(t)
Object.defineProperty(TestContextProto, namePath, {
get() {
this.diagnostic(usePathName)
const result = this.name
this.diagnostic(restoreName)
return result
},
})

return t[namePath]
} catch {}

return [t.name] // last resort
}

module.exports = { installLocationInNextTest, getCallerLocation }
module.exports = { createCallerLocationHook, getTestNamePath }
57 changes: 2 additions & 55 deletions src/jest.environment.js
Original file line number Diff line number Diff line change
@@ -1,57 +1,4 @@
// Shoult not import src/ stuff here, as this goes into runner too (to check config)

function getTestNamePath(t, { require } = {}) {
// No implementation in Node.js yet, will have to PR
if (t.fullName) return t.fullName.split(' > ')

// We are on Node.js < 22.3.0 where even t.fullName doesn't exist yet, polyfill
const namePath = Symbol('namePath')
const getNamePath = Symbol('getNamePath')
try {
if (t[namePath]) return t[namePath]

// Sigh, ok, whatever
const { Test } = require('node:internal/test_runner/test')

const usePathName = Symbol('usePathName')
const restoreName = Symbol('restoreName')
Test.prototype[getNamePath] = function () {
if (this === this.root) return []
return [...(this.parent?.[getNamePath]() || []), this.name]
}

const diagnostic = Test.prototype.diagnostic
Test.prototype.diagnostic = function (...args) {
if (args[0] === usePathName) {
this[restoreName] = this.name
this.name = this[getNamePath]()
return
}

if (args[0] === restoreName) {
this.name = this[restoreName]
delete this[restoreName]
return
}

return diagnostic.apply(this, args)
}

const TestContextProto = Object.getPrototypeOf(t)
Object.defineProperty(TestContextProto, namePath, {
get() {
this.diagnostic(usePathName)
const result = this.name
this.diagnostic(restoreName)
return result
},
})

return t[namePath]
} catch {}

return [t.name] // last resort
}
import { getTestNamePath } from './dark.cjs'

export const specialEnvironments = {
__proto__: null,
Expand Down Expand Up @@ -81,7 +28,7 @@ export const specialEnvironments = {

jestGlobals.beforeEach((t) => {
if (!pollyGlobals.isPollyActive) return
const name = getTestNamePath(t, { require }).join('/')
const name = getTestNamePath(t).join('/')
pollyGlobals.pollyContext.polly = new Polly(name, pollyGlobals.pollyContext.options)
})

Expand Down
4 changes: 3 additions & 1 deletion src/jest.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import { jestfn, allMocks } from './jest.fn.js'
import { jestmock, requireActual, requireMock, resetModules } from './jest.mock.js'
import * as jestTimers from './jest.timers.js'
import './jest.snapshot.js'
import { getCallerLocation, installLocationInNextTest } from './dark.cjs'
import { createCallerLocationHook } from './dark.cjs'
import { expect } from 'expect'
import matchers from 'jest-extended'

const { getCallerLocation, installLocationInNextTest } = createCallerLocationHook()

expect.extend(matchers)

let defaultTimeout = jestConfig().testTimeout // overridable via jest.setTimeout()
Expand Down
4 changes: 3 additions & 1 deletion src/tape.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assert from 'node:assert/strict'
import assertLoose from 'node:assert'
import { test } from 'node:test'
import { getCallerLocation, installLocationInNextTest } from './dark.cjs'
import { createCallerLocationHook } from './dark.cjs'

const knownOptions = new Set(['skip', 'todo', 'concurrency', 'timeout'])

Expand Down Expand Up @@ -134,6 +134,8 @@ function tapeWrapAssert(t, callback) {

const AsyncFunction = (async () => {}).constructor

const { getCallerLocation, installLocationInNextTest } = createCallerLocationHook()

function tapeWrap(test) {
const tap = (...args) => {
const fn = args.pop()
Expand Down

0 comments on commit 9454907

Please sign in to comment.