fix: workflow async concurrency (#13769)
* executeAsync * || 1 * wip * stepId * stepId * wip * wip * continue versioning management changes * fix and improve concurrency * update in memory engine * remove duplicated test * fix script * Create weak-drinks-confess.md * fixes * fix * fix * continuation * centralize merge checkepoint * centralize merge checkpoint * fix locking * rm only * Continue improvements and fixes * fixes * fixes * hasAwaiting will be recomputed * fix orchestrator engine * bump version on async parallel steps only * mark as delivered fix * changeset * check partitions * avoid saving when having parent step * cart test --------- Co-authored-by: Carlos R. L. Rodrigues <rodrigolr@gmail.com> Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com> Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
d97a60d3c1
commit
516f5a3896
@@ -8,6 +8,7 @@ import {
|
||||
normalizeImportPathWithSource,
|
||||
toMikroOrmEntities,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { logger } from "@medusajs/framework/logger"
|
||||
import * as fs from "fs"
|
||||
import { getDatabaseURL, getMikroOrmWrapper, TestDatabase } from "./database"
|
||||
import { initModules, InitModulesOptions } from "./init-modules"
|
||||
@@ -23,6 +24,24 @@ export interface SuiteOptions<TService = unknown> {
|
||||
}
|
||||
}
|
||||
|
||||
interface ModuleTestRunnerConfig<TService = any> {
|
||||
moduleName: string
|
||||
moduleModels?: any[]
|
||||
moduleOptions?: Record<string, any>
|
||||
moduleDependencies?: string[]
|
||||
joinerConfig?: any[]
|
||||
schema?: string
|
||||
dbName?: string
|
||||
injectedDependencies?: Record<string, any>
|
||||
resolve?: string
|
||||
debug?: boolean
|
||||
cwd?: string
|
||||
hooks?: {
|
||||
beforeModuleInit?: () => Promise<void>
|
||||
afterModuleInit?: (medusaApp: any, service: TService) => Promise<void>
|
||||
}
|
||||
}
|
||||
|
||||
function createMikroOrmWrapper(options: {
|
||||
moduleModels?: (Function | DmlEntity<any, any>)[]
|
||||
resolve?: string
|
||||
@@ -64,6 +83,220 @@ function createMikroOrmWrapper(options: {
|
||||
return { MikroOrmWrapper, models: moduleModels }
|
||||
}
|
||||
|
||||
class ModuleTestRunner<TService = any> {
|
||||
private moduleName: string
|
||||
private schema: string
|
||||
private dbName: string
|
||||
private dbConfig: {
|
||||
clientUrl: string
|
||||
schema: string
|
||||
debug: boolean
|
||||
}
|
||||
private debug: boolean
|
||||
private resolve?: string
|
||||
private cwd?: string
|
||||
private moduleOptions: Record<string, any>
|
||||
private moduleDependencies?: string[]
|
||||
private joinerConfig: any[]
|
||||
private injectedDependencies: Record<string, any>
|
||||
private hooks: ModuleTestRunnerConfig<TService>["hooks"] = {}
|
||||
|
||||
private connection: any = null
|
||||
private MikroOrmWrapper!: TestDatabase
|
||||
private moduleModels: (Function | DmlEntity<any, any>)[] = []
|
||||
private modulesConfig: any = {}
|
||||
private moduleOptionsConfig!: InitModulesOptions
|
||||
|
||||
private shutdown: () => Promise<void> = async () => void 0
|
||||
private moduleService: any = null
|
||||
private medusaApp: any = {}
|
||||
|
||||
constructor(config: ModuleTestRunnerConfig<TService>) {
|
||||
const tempName = parseInt(process.env.JEST_WORKER_ID || "1")
|
||||
this.moduleName = config.moduleName
|
||||
this.dbName =
|
||||
config.dbName ??
|
||||
`medusa-${config.moduleName.toLowerCase()}-integration-${tempName}`
|
||||
this.schema = config.schema ?? "public"
|
||||
this.debug = config.debug ?? false
|
||||
this.resolve = config.resolve
|
||||
this.cwd = config.cwd
|
||||
this.moduleOptions = config.moduleOptions ?? {}
|
||||
this.moduleDependencies = config.moduleDependencies
|
||||
this.joinerConfig = config.joinerConfig ?? []
|
||||
this.injectedDependencies = config.injectedDependencies ?? {}
|
||||
this.hooks = config.hooks ?? {}
|
||||
|
||||
this.dbConfig = {
|
||||
clientUrl: getDatabaseURL(this.dbName),
|
||||
schema: this.schema,
|
||||
debug: this.debug,
|
||||
}
|
||||
|
||||
this.setupProcessHandlers()
|
||||
this.initializeConfig(config.moduleModels)
|
||||
}
|
||||
|
||||
private setupProcessHandlers(): void {
|
||||
process.on("SIGTERM", async () => {
|
||||
await this.cleanup()
|
||||
process.exit(0)
|
||||
})
|
||||
|
||||
process.on("SIGINT", async () => {
|
||||
await this.cleanup()
|
||||
process.exit(0)
|
||||
})
|
||||
}
|
||||
|
||||
private initializeConfig(moduleModels?: any[]): void {
|
||||
const moduleSdkImports = require("@medusajs/framework/modules-sdk")
|
||||
|
||||
// Use a unique connection for all the entire suite
|
||||
this.connection = ModulesSdkUtils.createPgConnection(this.dbConfig)
|
||||
|
||||
const { MikroOrmWrapper, models } = createMikroOrmWrapper({
|
||||
moduleModels,
|
||||
resolve: this.resolve,
|
||||
dbConfig: this.dbConfig,
|
||||
cwd: this.cwd,
|
||||
})
|
||||
|
||||
this.MikroOrmWrapper = MikroOrmWrapper
|
||||
this.moduleModels = models
|
||||
|
||||
this.modulesConfig = {
|
||||
[this.moduleName]: {
|
||||
definition: moduleSdkImports.ModulesDefinition[this.moduleName],
|
||||
resolve: this.resolve,
|
||||
dependencies: this.moduleDependencies,
|
||||
options: {
|
||||
database: this.dbConfig,
|
||||
...this.moduleOptions,
|
||||
[isSharedConnectionSymbol]: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
this.moduleOptionsConfig = {
|
||||
injectedDependencies: {
|
||||
[ContainerRegistrationKeys.PG_CONNECTION]: this.connection,
|
||||
[Modules.EVENT_BUS]: new MockEventBusService(),
|
||||
[ContainerRegistrationKeys.LOGGER]: console,
|
||||
...this.injectedDependencies,
|
||||
},
|
||||
modulesConfig: this.modulesConfig,
|
||||
databaseConfig: this.dbConfig,
|
||||
joinerConfig: this.joinerConfig,
|
||||
preventConnectionDestroyWarning: true,
|
||||
cwd: this.cwd,
|
||||
}
|
||||
}
|
||||
|
||||
private createMedusaAppProxy(): any {
|
||||
return new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (target, prop) => {
|
||||
return this.medusaApp?.[prop]
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private createServiceProxy(): any {
|
||||
return new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (target, prop) => {
|
||||
return this.moduleService?.[prop]
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
public async beforeAll(): Promise<void> {
|
||||
try {
|
||||
this.setupProcessHandlers()
|
||||
process.env.LOG_LEVEL = "error"
|
||||
} catch (error) {
|
||||
await this.cleanup()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
public async beforeEach(): Promise<void> {
|
||||
try {
|
||||
if (this.moduleModels.length) {
|
||||
await this.MikroOrmWrapper.setupDatabase()
|
||||
}
|
||||
|
||||
if (this.hooks?.beforeModuleInit) {
|
||||
await this.hooks.beforeModuleInit()
|
||||
}
|
||||
|
||||
const output = await initModules(this.moduleOptionsConfig)
|
||||
this.shutdown = output.shutdown
|
||||
this.medusaApp = output.medusaApp
|
||||
this.moduleService = output.medusaApp.modules[this.moduleName]
|
||||
|
||||
if (this.hooks?.afterModuleInit) {
|
||||
await this.hooks.afterModuleInit(this.medusaApp, this.moduleService)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error in beforeEach:", error?.message)
|
||||
await this.cleanup()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
public async afterEach(): Promise<void> {
|
||||
try {
|
||||
if (this.moduleModels.length) {
|
||||
await this.MikroOrmWrapper.clearDatabase()
|
||||
}
|
||||
await this.shutdown()
|
||||
this.moduleService = {}
|
||||
this.medusaApp = {}
|
||||
} catch (error) {
|
||||
logger.error("Error in afterEach:", error?.message)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
public async cleanup(): Promise<void> {
|
||||
try {
|
||||
process.removeAllListeners("SIGTERM")
|
||||
process.removeAllListeners("SIGINT")
|
||||
|
||||
await (this.connection as any)?.context?.destroy()
|
||||
await (this.connection as any)?.destroy()
|
||||
|
||||
this.moduleService = null
|
||||
this.medusaApp = null
|
||||
this.connection = null
|
||||
|
||||
if (global.gc) {
|
||||
global.gc()
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error during cleanup:", error?.message)
|
||||
}
|
||||
}
|
||||
|
||||
public getOptions(): SuiteOptions<TService> {
|
||||
return {
|
||||
MikroOrmWrapper: this.MikroOrmWrapper,
|
||||
medusaApp: this.createMedusaAppProxy(),
|
||||
service: this.createServiceProxy(),
|
||||
dbConfig: {
|
||||
schema: this.schema,
|
||||
clientUrl: this.dbConfig.clientUrl,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function moduleIntegrationTestRunner<TService = any>({
|
||||
moduleName,
|
||||
moduleModels,
|
||||
@@ -76,6 +309,7 @@ export function moduleIntegrationTestRunner<TService = any>({
|
||||
resolve,
|
||||
injectedDependencies = {},
|
||||
cwd,
|
||||
hooks,
|
||||
}: {
|
||||
moduleName: string
|
||||
moduleModels?: any[]
|
||||
@@ -88,115 +322,68 @@ export function moduleIntegrationTestRunner<TService = any>({
|
||||
resolve?: string
|
||||
debug?: boolean
|
||||
cwd?: string
|
||||
hooks?: ModuleTestRunnerConfig<TService>["hooks"]
|
||||
testSuite: (options: SuiteOptions<TService>) => void
|
||||
}) {
|
||||
const moduleSdkImports = require("@medusajs/framework/modules-sdk")
|
||||
|
||||
process.env.LOG_LEVEL = "error"
|
||||
|
||||
const tempName = parseInt(process.env.JEST_WORKER_ID || "1")
|
||||
const dbName = `medusa-${moduleName.toLowerCase()}-integration-${tempName}`
|
||||
|
||||
const dbConfig = {
|
||||
clientUrl: getDatabaseURL(dbName),
|
||||
const runner = new ModuleTestRunner<TService>({
|
||||
moduleName,
|
||||
moduleModels,
|
||||
moduleOptions,
|
||||
moduleDependencies,
|
||||
joinerConfig,
|
||||
schema,
|
||||
debug,
|
||||
}
|
||||
|
||||
// Use a unique connection for all the entire suite
|
||||
const connection = ModulesSdkUtils.createPgConnection(dbConfig)
|
||||
|
||||
const { MikroOrmWrapper, models } = createMikroOrmWrapper({
|
||||
moduleModels,
|
||||
resolve,
|
||||
dbConfig,
|
||||
injectedDependencies,
|
||||
cwd,
|
||||
hooks,
|
||||
})
|
||||
|
||||
moduleModels = models
|
||||
|
||||
const modulesConfig_ = {
|
||||
[moduleName]: {
|
||||
definition: moduleSdkImports.ModulesDefinition[moduleName],
|
||||
resolve,
|
||||
dependencies: moduleDependencies,
|
||||
options: {
|
||||
database: dbConfig,
|
||||
...moduleOptions,
|
||||
[isSharedConnectionSymbol]: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const moduleOptions_: InitModulesOptions = {
|
||||
injectedDependencies: {
|
||||
[ContainerRegistrationKeys.PG_CONNECTION]: connection,
|
||||
[Modules.EVENT_BUS]: new MockEventBusService(),
|
||||
[ContainerRegistrationKeys.LOGGER]: console,
|
||||
...injectedDependencies,
|
||||
},
|
||||
modulesConfig: modulesConfig_,
|
||||
databaseConfig: dbConfig,
|
||||
joinerConfig,
|
||||
preventConnectionDestroyWarning: true,
|
||||
cwd,
|
||||
}
|
||||
|
||||
let shutdown: () => Promise<void>
|
||||
let moduleService
|
||||
let medusaApp = {}
|
||||
|
||||
const options = {
|
||||
MikroOrmWrapper,
|
||||
medusaApp: new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (target, prop) => {
|
||||
return medusaApp[prop]
|
||||
},
|
||||
}
|
||||
),
|
||||
service: new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (target, prop) => {
|
||||
return moduleService[prop]
|
||||
},
|
||||
}
|
||||
),
|
||||
dbConfig: {
|
||||
schema,
|
||||
clientUrl: dbConfig.clientUrl,
|
||||
},
|
||||
} as SuiteOptions<TService>
|
||||
|
||||
const beforeEach_ = async () => {
|
||||
if (moduleModels.length) {
|
||||
await MikroOrmWrapper.setupDatabase()
|
||||
}
|
||||
const output = await initModules(moduleOptions_)
|
||||
shutdown = output.shutdown
|
||||
medusaApp = output.medusaApp
|
||||
moduleService = output.medusaApp.modules[moduleName]
|
||||
}
|
||||
|
||||
const afterEach_ = async () => {
|
||||
if (moduleModels.length) {
|
||||
await MikroOrmWrapper.clearDatabase()
|
||||
}
|
||||
await shutdown()
|
||||
moduleService = {}
|
||||
medusaApp = {}
|
||||
}
|
||||
|
||||
return describe("", () => {
|
||||
beforeEach(beforeEach_)
|
||||
afterEach(afterEach_)
|
||||
afterAll(async () => {
|
||||
await (connection as any).context?.destroy()
|
||||
await (connection as any).destroy()
|
||||
let testOptions: SuiteOptions<TService>
|
||||
|
||||
beforeAll(async () => {
|
||||
await runner.beforeAll()
|
||||
testOptions = runner.getOptions()
|
||||
})
|
||||
|
||||
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())
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user