Chore/test runner improvements (#12439)

**What**
Make sure there is no open handles left and that the shutdown function are properly called. Refactor and improve the medusa test runner. Make sure all modules instances are released and cleaned up

**NOTE:**
On a separate PR we can continue the investigation for the memory growing over time while the tests execute
This commit is contained in:
Adrien de Peretti authored and GitHub committed 2025-05-14 13:17:41 +00:00
1 parent e60d15ea84
commit ab22faaa52
13 files changed
+467 -197

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
---
"@medusajs/test-utils": patch
"@medusajs/workflow-engine-inmemory": patch
"@medusajs/workflow-engine-redis": patch
---
Chore/test runner improvements
@@ -31,7 +31,7 @@ describe("waitSubscribersExecution", () => {
describe("with no existing listeners", () => { describe("with no existing listeners", () => {
it("should resolve when event is fired before timeout", async () => { it("should resolve when event is fired before timeout", async () => {
const waitPromise = waitSubscribersExecution(TEST_EVENT, eventBus as any) const waitPromise = waitSubscribersExecution(TEST_EVENT, eventBus as any)
setTimeout(() => eventBus.emit(TEST_EVENT, "test-data"), 100) setTimeout(() => eventBus.emit(TEST_EVENT, "test-data"), 100).unref()
jest.advanceTimersByTime(100) jest.advanceTimersByTime(100)
@@ -69,7 +69,7 @@ describe("waitSubscribersExecution", () => {
describe("with existing listeners", () => { describe("with existing listeners", () => {
it("should resolve when all listeners complete successfully", async () => { it("should resolve when all listeners complete successfully", async () => {
const listener = jest.fn().mockImplementation(() => { const listener = jest.fn().mockImplementation(() => {
return new Promise((resolve) => setTimeout(resolve, 200)) return new Promise((resolve) => setTimeout(resolve, 200).unref())
}) })
eventBus.eventEmitter_.on(TEST_EVENT, listener) eventBus.eventEmitter_.on(TEST_EVENT, listener)
@@ -131,15 +131,15 @@ describe("waitSubscribersExecution", () => {
describe("with multiple listeners", () => { describe("with multiple listeners", () => {
it("should resolve when all listeners complete", async () => { it("should resolve when all listeners complete", async () => {
const listener1 = jest.fn().mockImplementation(() => { const listener1 = jest.fn().mockImplementation(() => {
return new Promise((resolve) => setTimeout(resolve, 100)) return new Promise((resolve) => setTimeout(resolve, 100).unref())
}) })
const listener2 = jest.fn().mockImplementation(() => { const listener2 = jest.fn().mockImplementation(() => {
return new Promise((resolve) => setTimeout(resolve, 200)) return new Promise((resolve) => setTimeout(resolve, 200).unref())
}) })
const listener3 = jest.fn().mockImplementation(() => { const listener3 = jest.fn().mockImplementation(() => {
return new Promise((resolve) => setTimeout(resolve, 300)) return new Promise((resolve) => setTimeout(resolve, 300).unref())
}) })
eventBus.eventEmitter_.on(TEST_EVENT, listener1) eventBus.eventEmitter_.on(TEST_EVENT, listener1)
+69 -13
View File
@@ -5,6 +5,8 @@ import {
SqlEntityManager, SqlEntityManager,
} from "@mikro-orm/postgresql" } from "@mikro-orm/postgresql"
import { createDatabase, dropDatabase } from "pg-god" import { createDatabase, dropDatabase } from "pg-god"
import { logger } from "@medusajs/framework/logger"
import { execOrTimeout } from "./medusa-test-runner-utils"
const DB_HOST = process.env.DB_HOST ?? "localhost" const DB_HOST = process.env.DB_HOST ?? "localhost"
const DB_USERNAME = process.env.DB_USERNAME ?? "" const DB_USERNAME = process.env.DB_USERNAME ?? ""
@@ -123,15 +125,15 @@ export function getMikroOrmWrapper({
schema: this.schema, schema: this.schema,
}) })
// Initializing the ORM try {
this.orm = await MikroORM.init(OrmConfig) this.orm = await MikroORM.init(OrmConfig)
this.manager = this.orm.em this.manager = this.orm.em
try { try {
await this.orm.getSchemaGenerator().ensureDatabase() await this.orm.getSchemaGenerator().ensureDatabase()
} catch (err) { } catch (err) {
console.log(err) logger.error("Error ensuring database:", err)
throw err
} }
await this.manager?.execute( await this.manager?.execute(
@@ -147,7 +149,19 @@ export function getMikroOrmWrapper({
.getMigrator() .getMigrator()
.up({ migrations: pendingMigrations.map((m) => m.name!) }) .up({ migrations: pendingMigrations.map((m) => m.name!) })
} else { } else {
await this.orm.schema.refreshDatabase() // ensure db exists and is fresh await this.orm.schema.refreshDatabase()
}
} catch (error) {
if (this.orm) {
try {
await this.orm.close()
} catch (closeError) {
logger.error("Error closing ORM:", closeError)
}
}
this.orm = null
this.manager = null
throw error
} }
}, },
@@ -156,6 +170,7 @@ export function getMikroOrmWrapper({
throw new Error("ORM not configured") throw new Error("ORM not configured")
} }
try {
await this.manager?.execute( await this.manager?.execute(
`DROP SCHEMA IF EXISTS "${this.schema ?? "public"}" CASCADE;` `DROP SCHEMA IF EXISTS "${this.schema ?? "public"}" CASCADE;`
) )
@@ -164,12 +179,21 @@ export function getMikroOrmWrapper({
`CREATE SCHEMA IF NOT EXISTS "${this.schema ?? "public"}";` `CREATE SCHEMA IF NOT EXISTS "${this.schema ?? "public"}";`
) )
try { const closePromise = this.orm.close()
await this.orm.close()
} catch {}
await execOrTimeout(closePromise)
} catch (error) {
logger.error("Error clearing database:", error)
try {
await this.orm?.close()
} catch (closeError) {
logger.error("Error during forced ORM close:", closeError)
}
throw error
} finally {
this.orm = null this.orm = null
this.manager = null this.manager = null
}
}, },
} }
} }
@@ -178,10 +202,15 @@ export const dbTestUtilFactory = (): any => ({
pgConnection_: null, pgConnection_: null,
create: async function (dbName: string) { create: async function (dbName: string) {
try {
await createDatabase( await createDatabase(
{ databaseName: dbName, errorIfExist: false }, { databaseName: dbName, errorIfExist: false },
pgGodCredentials pgGodCredentials
) )
} catch (error) {
logger.error("Error creating database:", error)
throw error
}
}, },
teardown: async function ({ schema }: { schema?: string } = {}) { teardown: async function ({ schema }: { schema?: string } = {}) {
@@ -189,8 +218,8 @@ export const dbTestUtilFactory = (): any => ({
return return
} }
try {
const runRawQuery = this.pgConnection_.raw.bind(this.pgConnection_) const runRawQuery = this.pgConnection_.raw.bind(this.pgConnection_)
schema ??= "public" schema ??= "public"
await runRawQuery(`SET session_replication_role = 'replica';`) await runRawQuery(`SET session_replication_role = 'replica';`)
@@ -201,12 +230,12 @@ export const dbTestUtilFactory = (): any => ({
const skipIndexPartitionPrefix = "cat_" const skipIndexPartitionPrefix = "cat_"
const mainPartitionTables = ["index_data", "index_relation"] const mainPartitionTables = ["index_data", "index_relation"]
let hasIndexTables = false let hasIndexTables = false
for (const { table_name } of tableNames) { for (const { table_name } of tableNames) {
if (mainPartitionTables.includes(table_name)) { if (mainPartitionTables.includes(table_name)) {
hasIndexTables = true hasIndexTables = true
} }
// Skipping index partition tables.
if ( if (
table_name.startsWith(skipIndexPartitionPrefix) || table_name.startsWith(skipIndexPartitionPrefix) ||
mainPartitionTables.includes(table_name) mainPartitionTables.includes(table_name)
@@ -214,8 +243,7 @@ export const dbTestUtilFactory = (): any => ({
continue continue
} }
await runRawQuery(`DELETE await runRawQuery(`DELETE FROM ${schema}."${table_name}";`)
FROM ${schema}."${table_name}";`)
} }
if (hasIndexTables) { if (hasIndexTables) {
@@ -224,15 +252,43 @@ export const dbTestUtilFactory = (): any => ({
} }
await runRawQuery(`SET session_replication_role = 'origin';`) await runRawQuery(`SET session_replication_role = 'origin';`)
} catch (error) {
logger.error("Error during database teardown:", error)
throw error
}
}, },
shutdown: async function (dbName: string) { shutdown: async function (dbName: string) {
await this.pgConnection_?.context?.destroy() try {
await this.pgConnection_?.destroy() const cleanupPromises: Promise<any>[] = []
if (this.pgConnection_?.context) {
cleanupPromises.push(
execOrTimeout(this.pgConnection_.context.destroy())
)
}
if (this.pgConnection_) {
cleanupPromises.push(execOrTimeout(this.pgConnection_.destroy()))
}
await Promise.all(cleanupPromises)
return await dropDatabase( return await dropDatabase(
{ databaseName: dbName, errorIfNonExist: false }, { databaseName: dbName, errorIfNonExist: false },
pgGodCredentials pgGodCredentials
) )
} catch (error) {
logger.error("Error during database shutdown:", error)
try {
await this.pgConnection_?.context?.destroy()
await this.pgConnection_?.destroy()
} catch (cleanupError) {
logger.error("Error during forced cleanup:", cleanupError)
}
throw error
} finally {
this.pgConnection_ = null
}
}, },
}) })
+1
View File
@@ -25,6 +25,7 @@ export const waitSubscribersExecution = (
) )
) )
}, timeout) }, timeout)
timeoutId.unref()
}) })
// If there are no existing listeners, resolve once the event happens. Otherwise, wrap the existing subscribers in a promise and resolve once they are done. // If there are no existing listeners, resolve once the event happens. Otherwise, wrap the existing subscribers in a promise and resolve once they are done.
@@ -8,6 +8,7 @@ import {
createPgConnection, createPgConnection,
promiseAll, promiseAll,
} from "@medusajs/framework/utils" } from "@medusajs/framework/utils"
import { logger } from "@medusajs/framework/logger"
export interface InitModulesOptions { export interface InitModulesOptions {
injectedDependencies?: Record<string, unknown> injectedDependencies?: Record<string, unknown>
@@ -69,7 +70,7 @@ export async function initModules({
]) ])
} else { } else {
if (!preventConnectionDestroyWarning) { if (!preventConnectionDestroyWarning) {
console.info( logger.info(
`You are using a custom shared connection. The connection won't be destroyed automatically.` `You are using a custom shared connection. The connection won't be destroyed automatically.`
) )
} }
@@ -2,8 +2,9 @@ import express from "express"
import getPort from "get-port" import getPort from "get-port"
import { resolve } from "path" import { resolve } from "path"
import { MedusaContainer } from "@medusajs/framework/types" import { MedusaContainer } from "@medusajs/framework/types"
import { applyEnvVarsToProcess } from "./utils" import { applyEnvVarsToProcess, execOrTimeout } from "./utils"
import { promiseAll, GracefulShutdownServer } from "@medusajs/framework/utils" import { promiseAll, GracefulShutdownServer } from "@medusajs/framework/utils"
import { logger } from "@medusajs/framework/logger"
async function bootstrapApp({ async function bootstrapApp({
cwd, cwd,
@@ -14,6 +15,7 @@ async function bootstrapApp({
const loaders = require("@medusajs/medusa/loaders/index").default const loaders = require("@medusajs/medusa/loaders/index").default
try {
const { container, shutdown } = await loaders({ const { container, shutdown } = await loaders({
directory: resolve(cwd || process.cwd()), directory: resolve(cwd || process.cwd()),
expressApp: app, expressApp: app,
@@ -27,6 +29,10 @@ async function bootstrapApp({
app, app,
port: PORT, port: PORT,
} }
} catch (error) {
logger.error("Error bootstrapping app:", error)
throw error
}
} }
export async function startApp({ export async function startApp({
@@ -37,30 +43,53 @@ export async function startApp({
container: MedusaContainer container: MedusaContainer
port: number port: number
}> { }> {
let expressServer: any
let medusaShutdown: () => Promise<void> = async () => void 0
let container: MedusaContainer
try {
const { const {
app, app,
port, port,
container, container: appContainer,
shutdown: medusaShutdown, shutdown: appShutdown,
} = await bootstrapApp({ } = await bootstrapApp({
cwd, cwd,
env, env,
}) })
let expressServer container = appContainer
medusaShutdown = appShutdown
const shutdown = async () => { const shutdown = async () => {
await promiseAll([expressServer?.shutdown(), medusaShutdown()]) try {
const shutdownPromise = promiseAll([
expressServer?.shutdown(),
medusaShutdown(),
])
await execOrTimeout(shutdownPromise)
if (typeof global !== "undefined" && global?.gc) { if (typeof global !== "undefined" && global?.gc) {
global.gc() global.gc()
} }
} catch (error) {
logger.error("Error during shutdown:", error)
try {
await expressServer?.shutdown()
await medusaShutdown()
} catch (cleanupError) {
logger.error("Error during forced cleanup:", cleanupError)
}
throw error
}
} }
return await new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
const server = app const server = app
.listen(port) .listen(port)
.on("error", async (err) => { .on("error", async (err) => {
logger.error("Error starting server:", err)
await shutdown() await shutdown()
return reject(err) return reject(err)
}) })
@@ -74,7 +103,24 @@ export async function startApp({
}) })
}) })
// TODO: fix that once we find the appropriate place to put this util
expressServer = GracefulShutdownServer.create(server) expressServer = GracefulShutdownServer.create(server)
}) })
} catch (error) {
logger.error("Error in startApp:", error)
if (expressServer) {
try {
await expressServer.shutdown()
} catch (cleanupError) {
logger.error("Error cleaning up express server:", cleanupError)
}
}
if (medusaShutdown) {
try {
await medusaShutdown()
} catch (cleanupError) {
logger.error("Error cleaning up medusa:", cleanupError)
}
}
throw error
}
} }
@@ -1,5 +1,6 @@
import type { MedusaAppLoader } from "@medusajs/framework" import type { MedusaAppLoader } from "@medusajs/framework"
import { Logger, MedusaContainer } from "@medusajs/framework/types" import { Logger, MedusaContainer } from "@medusajs/framework/types"
import { logger } from "@medusajs/framework/logger"
import { import {
ContainerRegistrationKeys, ContainerRegistrationKeys,
getResolvedPlugins, getResolvedPlugins,
@@ -27,7 +28,7 @@ export async function migrateDatabase(appLoader: MedusaAppLoader) {
try { try {
await appLoader.runModulesMigrations() await appLoader.runModulesMigrations()
} catch (err) { } catch (err) {
console.error("Something went wrong while running the migrations") logger.error("Something went wrong while running the migrations")
throw err throw err
} }
} }
@@ -5,3 +5,23 @@ export function applyEnvVarsToProcess(env?: Record<any, any>) {
Object.entries(env).forEach(([k, v]) => (process.env[k] = v)) Object.entries(env).forEach(([k, v]) => (process.env[k] = v))
} }
} }
/**
* Execute a function and return a promise that resolves when the function
* resolves or rejects when the function rejects or the timeout is reached.
* @param fn - The function to execute.
* @param timeout - The timeout in milliseconds.
* @returns A promise that resolves when the function resolves or rejects when the function rejects or the timeout is reached.
*/
export async function execOrTimeout(
fn: Promise<any> | (() => Promise<void>),
timeout: number = 5000
) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("Timeout")), timeout).unref()
})
const fnPromise = typeof fn === "function" ? fn() : fn
return Promise.race([fnPromise, timeoutPromise])
}
@@ -1,10 +1,11 @@
import { MedusaAppOutput } from "@medusajs/framework/modules-sdk" import { MedusaAppOutput } from "@medusajs/framework/modules-sdk"
import { ContainerLike, MedusaContainer } from "@medusajs/framework/types" import { MedusaContainer } from "@medusajs/framework/types"
import { import {
ContainerRegistrationKeys, ContainerRegistrationKeys,
createMedusaContainer, createMedusaContainer,
} from "@medusajs/framework/utils" } from "@medusajs/framework/utils"
import { asValue } from "awilix" import { asValue } from "awilix"
import { logger } from "@medusajs/framework/logger"
import { dbTestUtilFactory, getDatabaseURL } from "./database" import { dbTestUtilFactory, getDatabaseURL } from "./database"
import { import {
applyEnvVarsToProcess, applyEnvVarsToProcess,
@@ -33,6 +34,254 @@ export interface MedusaSuiteOptions {
getMedusaApp: () => MedusaAppOutput getMedusaApp: () => MedusaAppOutput
} }
interface TestRunnerConfig {
moduleName?: string
env?: Record<string, any>
dbName?: string
medusaConfigFile?: string
schema?: string
debug?: boolean
inApp?: boolean
}
class MedusaTestRunner {
private dbName: string
private schema: string
private cwd: string
private env: Record<string, any>
private debug: boolean
// @ts-ignore
private inApp: boolean
private dbUtils: ReturnType<typeof dbTestUtilFactory>
private dbConfig: {
dbName: string
clientUrl: string
schema: string
debug: boolean
}
private globalContainer: MedusaContainer | null = null
private apiUtils: any = null
private loadedApplication: any = null
private shutdown: () => Promise<void> = async () => void 0
private isFirstTime = true
constructor(config: TestRunnerConfig) {
const tempName = parseInt(process.env.JEST_WORKER_ID || "1")
const moduleName =
config.moduleName ?? Math.random().toString(36).substring(7)
this.dbName =
config.dbName ??
`medusa-${moduleName.toLowerCase()}-integration-${tempName}`
this.schema = config.schema ?? "public"
this.cwd = config.medusaConfigFile ?? process.cwd()
this.env = config.env ?? {}
this.debug = config.debug ?? false
this.inApp = config.inApp ?? false
this.dbUtils = dbTestUtilFactory()
this.dbConfig = {
dbName: this.dbName,
clientUrl: getDatabaseURL(this.dbName),
schema: this.schema,
debug: this.debug,
}
this.setupProcessHandlers()
}
private setupProcessHandlers(): void {
process.on("SIGTERM", async () => {
await this.cleanup()
process.exit(0)
})
process.on("SIGINT", async () => {
await this.cleanup()
process.exit(0)
})
}
private createApiProxy(): any {
return new Proxy(
{},
{
get: (target, prop) => {
return this.apiUtils?.[prop]
},
}
)
}
private createDbConnectionProxy(): any {
return new Proxy(
{},
{
get: (target, prop) => {
return this.dbUtils.pgConnection_?.[prop]
},
}
)
}
private async initializeDatabase(): Promise<void> {
try {
logger.info(`Creating database ${this.dbName}`)
await this.dbUtils.create(this.dbName)
this.dbUtils.pgConnection_ = await initDb()
} catch (error) {
logger.error(`Error initializing database: ${error?.message}`)
await this.cleanup()
throw error
}
}
private async setupApplication(): Promise<void> {
const { container, MedusaAppLoader } = await import("@medusajs/framework")
const appLoader = new MedusaAppLoader()
container.register({
[ContainerRegistrationKeys.LOGGER]: asValue(logger),
})
await this.initializeDatabase()
logger.info(
`Migrating database with core migrations and links ${this.dbName}`
)
await migrateDatabase(appLoader)
await syncLinks(appLoader, this.cwd, container, logger)
await clearInstances()
this.loadedApplication = await appLoader.load()
try {
const {
shutdown,
container: appContainer,
port,
} = await startApp({
cwd: this.cwd,
env: this.env,
})
this.globalContainer = appContainer
this.shutdown = async () => {
await shutdown()
if (this.apiUtils?.cancelToken?.source) {
this.apiUtils.cancelToken.source.cancel(
"Request canceled by shutdown"
)
}
}
const { default: axios } = (await import("axios")) as any
const cancelTokenSource = axios.CancelToken.source()
this.apiUtils = axios.create({
baseURL: `http://localhost:${port}`,
cancelToken: cancelTokenSource.token,
})
this.apiUtils.cancelToken = { source: cancelTokenSource }
} catch (error) {
logger.error(`Error starting the app: ${error?.message}`)
await this.cleanup()
throw error
}
}
public async cleanup(): Promise<void> {
try {
process.removeAllListeners("SIGTERM")
process.removeAllListeners("SIGINT")
await this.dbUtils.shutdown(this.dbName)
await this.shutdown()
await clearInstances()
if (this.apiUtils?.cancelToken?.source) {
this.apiUtils.cancelToken.source.cancel("Cleanup")
}
if (this.globalContainer?.dispose) {
await this.globalContainer.dispose()
}
this.apiUtils = null
this.loadedApplication = null
this.globalContainer = null
if (global.gc) {
global.gc()
}
} catch (error) {
logger.error("Error during cleanup:", error?.message)
}
}
public async beforeAll(): Promise<void> {
try {
this.setupProcessHandlers()
await configLoaderOverride(this.cwd, this.dbConfig)
applyEnvVarsToProcess(this.env)
await this.setupApplication()
} catch (error) {
await this.cleanup()
throw error
}
}
public async beforeEach(): Promise<void> {
if (this.isFirstTime) {
this.isFirstTime = false
return
}
await this.afterEach()
const container = this.globalContainer as MedusaContainer
const copiedContainer = createMedusaContainer({}, container)
try {
const { MedusaAppLoader } = await import("@medusajs/framework")
const medusaAppLoader = new MedusaAppLoader({
container: copiedContainer,
})
await medusaAppLoader.runModulesLoader()
} catch (error) {
await copiedContainer.dispose?.()
logger.error("Error running modules loaders:", error?.message)
throw error
}
}
public async afterEach(): Promise<void> {
try {
await this.dbUtils.teardown({ schema: this.schema })
} catch (error) {
logger.error("Error tearing down database:", error?.message)
throw error
}
}
public getOptions(): MedusaSuiteOptions {
return {
api: this.createApiProxy(),
dbConnection: this.createDbConnectionProxy(),
getMedusaApp: () => this.loadedApplication,
getContainer: () => this.globalContainer as MedusaContainer,
dbConfig: {
dbName: this.dbName,
schema: this.schema,
clientUrl: this.dbConfig.clientUrl,
},
dbUtils: this.dbUtils,
}
}
}
export function medusaIntegrationTestRunner({ export function medusaIntegrationTestRunner({
moduleName, moduleName,
dbName, dbName,
@@ -52,172 +301,61 @@ export function medusaIntegrationTestRunner({
inApp?: boolean inApp?: boolean
testSuite: (options: MedusaSuiteOptions) => void testSuite: (options: MedusaSuiteOptions) => void
}) { }) {
const tempName = parseInt(process.env.JEST_WORKER_ID || "1") const runner = new MedusaTestRunner({
moduleName = moduleName ?? Math.random().toString(36).substring(7) moduleName,
dbName ??= `medusa-${moduleName.toLowerCase()}-integration-${tempName}`
let dbConfig = {
dbName, dbName,
clientUrl: getDatabaseURL(dbName), medusaConfigFile,
schema, schema,
debug,
}
const cwd = medusaConfigFile ?? process.cwd()
let shutdown = async () => void 0
const dbUtils = dbTestUtilFactory()
let globalContainer: ContainerLike
let apiUtils: any
let loadedApplication: any
let options = {
api: new Proxy(
{},
{
get: (target, prop) => {
return apiUtils[prop]
},
}
),
dbConnection: new Proxy(
{},
{
get: (target, prop) => {
return dbUtils.pgConnection_[prop]
},
}
),
getMedusaApp: () => loadedApplication,
getContainer: () => globalContainer,
dbConfig: {
dbName,
schema,
clientUrl: dbConfig.clientUrl,
},
dbUtils,
} as MedusaSuiteOptions
let isFirstTime = true
const beforeAll_ = async () => {
await configLoaderOverride(cwd, dbConfig)
applyEnvVarsToProcess(env)
const { logger, container, MedusaAppLoader } = await import(
"@medusajs/framework"
)
const appLoader = new MedusaAppLoader()
container.register({
[ContainerRegistrationKeys.LOGGER]: asValue(logger),
})
try {
logger.info(`Creating database ${dbName}`)
await dbUtils.create(dbName)
dbUtils.pgConnection_ = await initDb()
} catch (error) {
logger.error(`Error initializing database: ${error?.message}`)
throw error
}
logger.info(`Migrating database with core migrations and links ${dbName}`)
await migrateDatabase(appLoader)
await syncLinks(appLoader, cwd, container, logger)
await clearInstances()
let containerRes: MedusaContainer = container
let serverShutdownRes: () => any
let portRes: number
loadedApplication = await appLoader.load()
try {
const {
shutdown = () => void 0,
container: appContainer,
port,
} = await startApp({
cwd,
env, env,
debug,
inApp,
}) })
containerRes = appContainer
serverShutdownRes = shutdown
portRes = port
} catch (error) {
logger.error(`Error starting the app: error?.message`)
throw error
}
/**
* Run application migrations and sync links when inside
* an application
*/
if (inApp) {
logger.info(`Migrating database with core migrations and links ${dbName}`)
await migrateDatabase(appLoader)
await syncLinks(appLoader, cwd, containerRes, logger)
}
const { default: axios } = (await import("axios")) as any
const cancelTokenSource = axios.CancelToken.source()
globalContainer = containerRes
shutdown = async () => {
await serverShutdownRes()
cancelTokenSource.cancel("Request canceled by shutdown")
}
apiUtils = axios.create({
baseURL: `http://localhost:${portRes}`,
cancelToken: cancelTokenSource.token,
})
}
const beforeEach_ = async () => {
// The beforeAll already run everything, so lets not re run the loaders for the first iteration
if (isFirstTime) {
isFirstTime = false
return
}
const container = options.getContainer()
const copiedContainer = createMedusaContainer({}, container)
try {
const { MedusaAppLoader } = await import("@medusajs/framework")
const medusaAppLoader = new MedusaAppLoader({
container: copiedContainer,
})
await medusaAppLoader.runModulesLoader()
} catch (error) {
console.error("Error runner modules loaders", error?.message)
throw error
}
}
const afterEach_ = async () => {
try {
await dbUtils.teardown({ schema })
} catch (error) {
console.error("Error tearing down database:", error?.message)
throw error
}
}
return describe("", () => { return describe("", () => {
beforeAll(beforeAll_) let testOptions: MedusaSuiteOptions
beforeEach(beforeEach_)
afterEach(afterEach_) beforeAll(async () => {
afterAll(async () => { await runner.beforeAll()
await dbUtils.shutdown(dbName) testOptions = runner.getOptions()
await shutdown()
}) })
testSuite(options!) beforeEach(async () => {
await runner.beforeEach()
})
afterEach(async () => {
await runner.afterEach()
})
afterAll(async () => {
// Run main cleanup
await runner.cleanup()
// Clean references to the test options
for (const key in testOptions) {
if (typeof testOptions[key] === "function") {
testOptions[key] = null
} else if (
typeof testOptions[key] === "object" &&
testOptions[key] !== null
) {
Object.keys(testOptions[key]).forEach((k) => {
testOptions[key][k] = null
})
testOptions[key] = null
}
}
// Encourage garbage collection
// @ts-ignore
testOptions = null
if (global.gc) {
global.gc()
}
})
// Run test suite with options
testSuite(runner.getOptions())
}) })
} }
+1 -1
View File
@@ -268,7 +268,7 @@ async function start(args: {
if (!isShuttingDown) { if (!isShuttingDown) {
cluster.fork() cluster.fork()
} else if (!isPresent(cluster.workers)) { } else if (!isPresent(cluster.workers)) {
setTimeout(killMainProccess, 100) setTimeout(killMainProccess, 100).unref()
} }
}) })
@@ -328,7 +328,7 @@ export class DataSynchronizer {
break break
} }
await setTimeout(0) await setTimeout(0, undefined, { ref: false })
} }
let acknoledgement: { lastCursor: string; done?: boolean; err?: Error } = { let acknoledgement: { lastCursor: string; done?: boolean; err?: Error } = {
@@ -193,7 +193,7 @@ export class InMemoryLockingProvider implements ILockingProvider {
setTimeout(() => { setTimeout(() => {
cancellationToken.cancelled = true cancellationToken.cancelled = true
reject(new Error("Timed-out acquiring lock.")) reject(new Error("Timed-out acquiring lock."))
}, seconds * 1000) }, seconds * 1000).unref()
}) })
} }
} }
@@ -70,14 +70,6 @@ export class WorkflowsModuleService<
} }
__hooks = { __hooks = {
onApplicationShutdown: async () => {
await this.workflowOrchestratorService_.onApplicationShutdown()
await this.redisDisconnectHandler_()
clearInterval(this.clearTimeout_)
},
onApplicationPrepareShutdown: async () => {
await this.workflowOrchestratorService_.onApplicationPrepareShutdown()
},
onApplicationStart: async () => { onApplicationStart: async () => {
await this.workflowOrchestratorService_.onApplicationStart() await this.workflowOrchestratorService_.onApplicationStart()
@@ -88,6 +80,14 @@ export class WorkflowsModuleService<
} catch {} } catch {}
}, 1000 * 60 * 60) }, 1000 * 60 * 60)
}, },
onApplicationPrepareShutdown: async () => {
await this.workflowOrchestratorService_.onApplicationPrepareShutdown()
},
onApplicationShutdown: async () => {
await this.workflowOrchestratorService_.onApplicationShutdown()
await this.redisDisconnectHandler_()
clearInterval(this.clearTimeout_)
},
} }
static prepareFilters<T>(filters: T & { q?: string }) { static prepareFilters<T>(filters: T & { q?: string }) {