From cba12be4b1d35d78da0e7c8855680e0e3fc24467 Mon Sep 17 00:00:00 2001 From: IDONTSUDO Date: Mon, 11 Sep 2023 19:49:45 +0300 Subject: [PATCH] stack service and trigger service --- server/src/core/di/register_di.ts | 14 +- server/src/core/extensions/array.ts | 31 ++++ server/src/core/extensions/extensions.ts | 5 + server/src/core/helper/typed_event.ts | 13 +- server/src/core/model/exec_error_model.ts | 9 +- server/src/core/model/process_model.ts | 23 ++- .../core/services/executor_program_service.ts | 21 ++- .../services/files_change_notifier_service.ts | 52 +++++-- server/src/core/services/stack_service.ts | 137 +++++++++++++++++ server/src/core/services/trigger_service.ts | 140 ++++++++++++++++++ server/test/executor_program_service_test.ts | 96 ------------ .../features/executor_program_service_test.ts | 110 ++++++++++++++ .../files_change_notifier_service_test.ts | 20 +-- server/test/features/stack_service_test.ts | 114 ++++++++++++++ server/test/features/trigger_service_test.ts | 137 +++++++++++++++++ server/test/test.ts | 39 ++--- 16 files changed, 784 insertions(+), 177 deletions(-) create mode 100644 server/src/core/extensions/array.ts create mode 100644 server/src/core/extensions/extensions.ts create mode 100644 server/src/core/services/stack_service.ts create mode 100644 server/src/core/services/trigger_service.ts delete mode 100644 server/test/executor_program_service_test.ts create mode 100644 server/test/features/executor_program_service_test.ts rename server/test/{ => features}/files_change_notifier_service_test.ts (79%) create mode 100644 server/test/features/stack_service_test.ts create mode 100644 server/test/features/trigger_service_test.ts diff --git a/server/src/core/di/register_di.ts b/server/src/core/di/register_di.ts index 4ccb1e8..b08da3a 100644 --- a/server/src/core/di/register_di.ts +++ b/server/src/core/di/register_di.ts @@ -1,14 +1,17 @@ import { override } from "first-di"; -import { DevEnv, IEnv , UnitTestEnv } from "./env.js"; +import { DevEnv, IEnv, UnitTestEnv } from "./env.js"; import { MetaDataFileManagerModel } from "../model/meta_data_file_manager_model.js"; - -export default function locator(env: IEnv){ +import { extensions } from "../extensions/extensions.js"; + +export default function locator(env: IEnv) { + extensions(); envRegister(env); registerRepository(env); registerController(env); registerService(env); - override(MetaDataFileManagerModel, MetaDataFileManagerModel) + override(MetaDataFileManagerModel, MetaDataFileManagerModel); } + const envRegister = (env: IEnv) => { switch (env.toStringEnv()) { case UnitTestEnv.env(): @@ -19,6 +22,7 @@ const envRegister = (env: IEnv) => { return; } }; + const registerRepository = (env: IEnv) => { switch (env.toStringEnv()) { case UnitTestEnv.env(): @@ -30,6 +34,7 @@ const registerRepository = (env: IEnv) => { return; } }; + const registerController = (env: IEnv) => { switch (env.toStringEnv()) { case UnitTestEnv.env(): @@ -38,6 +43,7 @@ const registerController = (env: IEnv) => { return; } }; + const registerService = (env: IEnv) => { switch (env.toStringEnv()) { case UnitTestEnv.env(): diff --git a/server/src/core/extensions/array.ts b/server/src/core/extensions/array.ts new file mode 100644 index 0000000..46e38d1 --- /dev/null +++ b/server/src/core/extensions/array.ts @@ -0,0 +1,31 @@ +export {}; + +declare global { + interface Array { + // @strict: The parameter is determined whether the arrays must be exactly the same in content and order of this relationship or simply follow the same requirements. + equals(array: Array, strict: boolean): boolean; + } +} + +export const ArrayEquals = () => { + if ([].equals == undefined) { + Array.prototype.equals = function (array, strict = true) { + if (!array) return false; + + if (arguments.length == 1) strict = true; + + if (this.length != array.length) return false; + + for (let i = 0; i < this.length; i++) { + if (this[i] instanceof Array && array[i] instanceof Array) { + if (!this[i].equals(array[i], strict)) return false; + } else if (strict && this[i] != array[i]) { + return false; + } else if (!strict) { + return this.sort().equals(array.sort(), true); + } + } + return true; + }; + } +}; diff --git a/server/src/core/extensions/extensions.ts b/server/src/core/extensions/extensions.ts new file mode 100644 index 0000000..0a7d97a --- /dev/null +++ b/server/src/core/extensions/extensions.ts @@ -0,0 +1,5 @@ +import { ArrayEquals } from "./array.js"; + +export const extensions = () =>{ + ArrayEquals() +} \ No newline at end of file diff --git a/server/src/core/helper/typed_event.ts b/server/src/core/helper/typed_event.ts index 3dbc96e..d194064 100644 --- a/server/src/core/helper/typed_event.ts +++ b/server/src/core/helper/typed_event.ts @@ -9,7 +9,7 @@ export interface Disposable { export class TypedEvent { private listeners: Listener[] = []; - public listenersOncer: Listener[] = []; + public listenersOnces: Listener[] = []; on = (listener: Listener): Disposable => { this.listeners.push(listener); @@ -19,7 +19,7 @@ export class TypedEvent { }; once = (listener: Listener): void => { - this.listenersOncer.push(listener); + this.listenersOnces.push(listener); }; off = (listener: Listener) => { @@ -31,15 +31,14 @@ export class TypedEvent { }; emit = (event: T) => { - /** Обновить слушателей */ + this.listeners.forEach((listener) => listener(event) ); - /** Очистить очередь единожды */ - if (this.listenersOncer.length > 0) { - const toCall = this.listenersOncer; - this.listenersOncer = []; + if (this.listenersOnces.length > 0) { + const toCall = this.listenersOnces; + this.listenersOnces = []; toCall.forEach((listener) => listener(event)); } }; diff --git a/server/src/core/model/exec_error_model.ts b/server/src/core/model/exec_error_model.ts index 4fd2dc5..d9688cc 100644 --- a/server/src/core/model/exec_error_model.ts +++ b/server/src/core/model/exec_error_model.ts @@ -1,6 +1,6 @@ export class ExecError extends Error { static isExecError(e: any) { - if ("type" in e && "script" in e && "unixTime" in e) { + if ("type" in e && "script" in e && "unixTime" in e && 'error' in e) { return new ExecError(e.type, e.event, e.data); } return; @@ -8,14 +8,15 @@ export class ExecError extends Error { script: string; unixTime: number; type = EXEC_TYPE.EXEC; - + error:any; constructor( script: string, ...args: any ) { super(...args); - this.script = script; + this.script = script; this.unixTime = Date.now(); + this.error = args[0] } } @@ -50,10 +51,12 @@ export class SpawnError extends Error { return; } } + export enum EXEC_TYPE { SPAWN = "SPAWN", EXEC = "EXEC", } + export enum EXEC_EVENT { PROGRESS = "PROGRESS", ERROR = "ERROR", diff --git a/server/src/core/model/process_model.ts b/server/src/core/model/process_model.ts index 474d82a..da2239b 100644 --- a/server/src/core/model/process_model.ts +++ b/server/src/core/model/process_model.ts @@ -1,20 +1,18 @@ -export class CalculationProcess { - process: ProcessMetaData[]; - constructor(process: ProcessMetaData[]) { - this.process = process; - } -} +import { EXEC_TYPE } from "./exec_error_model.js"; + export interface ProcessMetaData { process: Process; trigger: Trigger; env: Env | null; + stackGenerateType:StackGenerateType; } -export enum ProcessType { - EXEC = "EXEC", - SPAWN = "SPAWN", +export enum StackGenerateType{ + MAP = 'MAP', + SINGLETON = 'SINGLETON' } + export interface Env { ssh_key: string; isUserInput: boolean; @@ -22,12 +20,13 @@ export interface Env { } export interface Process { - type: ProcessType; + type: EXEC_TYPE; command: string; isGenerating: boolean; isLocaleCode: boolean; issueType: IssueType; - timeout: number; + timeout?: number; + commit?:string | undefined; } export enum IssueType { @@ -41,5 +40,5 @@ export enum TriggerType { } export interface Trigger { type: TriggerType; - value: string; + value: string[]; } diff --git a/server/src/core/services/executor_program_service.ts b/server/src/core/services/executor_program_service.ts index 00dfe30..a6f89e2 100644 --- a/server/src/core/services/executor_program_service.ts +++ b/server/src/core/services/executor_program_service.ts @@ -24,7 +24,12 @@ export class ExecutorProgramService this.execPath = execPath; this.maxTime = maxTime; } - private async workerExecuted(command: string, workerType: WorkerType,args:Array | undefined = undefined) { + + private async workerExecuted( + command: string, + workerType: WorkerType, + args: Array | undefined = undefined + ) { cluster.setupPrimary({ exec: "./src/core/helper/worker_computed.js", }); @@ -39,22 +44,25 @@ export class ExecutorProgramService command: command, execPath: this.execPath, type: workerType, - cliArgs:args + cliArgs: args, }; worker.send(workerDataExec); worker.on("message", (e) => { const spawnError = SpawnError.isError(e); if (spawnError instanceof SpawnError) { this.emit(Result.error(spawnError)); + return; } const executorResult = ExecutorResult.isExecutorResult(e); if (executorResult instanceof ExecutorResult) { this.emit(Result.ok(executorResult)); + return; } const execError = ExecError.isExecError(e); if (execError instanceof ExecError) { this.emit(Result.error(execError)); + return; } }); if (this.maxTime != null) { @@ -70,13 +78,18 @@ export class ExecutorProgramService }, this.maxTime!); } } - public async call(type: EXEC_TYPE, command: string, args:Array | undefined = undefined): Promise { + + public async call( + type: EXEC_TYPE, + command: string, + args: Array | undefined = undefined + ): Promise { if (type == EXEC_TYPE.EXEC) { this.workerExecuted(command, WorkerType.EXEC); return; } - this.workerExecuted(command, WorkerType.SPAWN,args); + this.workerExecuted(command, WorkerType.SPAWN, args); return; } } diff --git a/server/src/core/services/files_change_notifier_service.ts b/server/src/core/services/files_change_notifier_service.ts index ba6280f..33a3615 100644 --- a/server/src/core/services/files_change_notifier_service.ts +++ b/server/src/core/services/files_change_notifier_service.ts @@ -4,21 +4,48 @@ import { promisify } from "node:util"; import { createHash } from "node:crypto"; import "reflect-metadata"; import { BinaryLike } from "crypto"; -import { EventsFileChanger, MetaDataFileManagerModel } from "../model/meta_data_file_manager_model.js"; +import { + EventsFileChanger, + MetaDataFileManagerModel, +} from "../model/meta_data_file_manager_model.js"; import { Result } from "../helper/result.js"; import { TypedEvent } from "../helper/typed_event.js"; const readFileAsync = promisify(fs.readFile); const readdir = promisify(fs.readdir); const stat = promisify(fs.stat); +const lsStat = promisify(fs.lstat); -function md5(content: Buffer | BinaryLike) { - return createHash("md5").update(content).digest("hex"); +function joinBuffers(buffers, delimiter = " ") { + const d = Buffer.from(delimiter); + return buffers.reduce((prev, b) => Buffer.concat([prev, d, b])); } - -interface IHashesCache { + +async function readFileAtBuffer(path: string): Promise { + if ((await lsStat(path)).isDirectory()) { + return ( + await readdir(path, { + encoding: "buffer", + }) + ).reduce( + (accumulator, currentValue) => joinBuffers([accumulator, currentValue]), + Buffer.from("") + ); + } + return await readFileAsync(path); +} +function md5(content: Buffer | BinaryLike): Promise { + return new Promise((resolve, _reject) => { + return resolve(createHash("md5").update(content).digest("hex")); + }); +} + +export interface IHashesCache { [key: string]: MetaDataFileManagerModel; } + + + export abstract class IFilesChangeNotifierService { abstract directory: string; } @@ -42,7 +69,7 @@ export class FilesChangeNotifierService } async setHash(file: string) { const data = await readFileAsync(file); - const md5Current = md5(data); + const md5Current = await md5(data); this.hashes[file] = new MetaDataFileManagerModel( file, @@ -77,16 +104,16 @@ export class FilesChangeNotifierService fsWait = false; }, 100); try { - const file = await readFileAsync(filePath); - const md5Current = md5(file); - + const file = await readFileAtBuffer(filePath); + const md5Current = await md5(file); if (md5Current === md5Previous) { return; } - const status = this.hashes[filePath] === undefined + const status = + this.hashes[filePath] === undefined ? EventsFileChanger.create : EventsFileChanger.update; - + const model = new MetaDataFileManagerModel( filePath, md5Current, @@ -115,8 +142,7 @@ export class FilesChangeNotifierService cancel() { if (this.watcher != undefined) { this.watcher.close(); - this.listenersOncer = [] + this.listenersOnces = []; } - } } diff --git a/server/src/core/services/stack_service.ts b/server/src/core/services/stack_service.ts new file mode 100644 index 0000000..a5874c3 --- /dev/null +++ b/server/src/core/services/stack_service.ts @@ -0,0 +1,137 @@ +import { + FilesChangeNotifierService, + IHashesCache, +} from "./files_change_notifier_service.js"; +import { ProcessMetaData, Trigger } from "../model/process_model.js"; +import { ExecutorProgramService } from "./executor_program_service.js"; +import { + EXEC_EVENT, + ExecError, + SpawnError, +} from "../model/exec_error_model.js"; +import { TypedEvent } from "../helper/typed_event.js"; +import { Result } from "../helper/result.js"; +import { ExecutorResult } from "../model/executor_result.js"; +import { delay } from "../helper/delay.js"; +import { TriggerErrorReport, TriggerService } from "./trigger_service.js"; + +export interface Iteration { + hashes: IHashesCache | null; + process: ProcessMetaData; + result?: ExecError | SpawnError | ExecutorResult; +} + +export abstract class IStackService { + abstract callStack: Iteration[]; + abstract path: string; + abstract init(processed: ProcessMetaData[], path: string): void; +} + +export class StackService extends TypedEvent implements IStackService { + callStack: Iteration[]; + path: string; + constructor(processed: ProcessMetaData[], path: string) { + super(); + this.path = path; + this.callStack = []; + this.init(processed); + } + + public init(processed: ProcessMetaData[]) { + for (let el of processed) { + el = this.commandHandler(el); + this.callStack.push({ + hashes: null, + process: el, + }); + } + } + private commandHandler(processMetaData: ProcessMetaData) { + processMetaData.process.command = processMetaData.process.command.replace( + "$PATH", + this.path + ); + return processMetaData; + } + public async call() { + let inc = 0; + + for await (const el of this.callStack!) { + await this.execStack(inc, el); + inc += 1; + } + } + async execStack( + stackNumber: number, + stackLayer: Iteration + ): Promise { + const executorService = new ExecutorProgramService(this.path); + + executorService.call( + stackLayer.process.process.type, + stackLayer.process.process.command + ); + + const filesChangeNotifierService = new FilesChangeNotifierService( + this.path + ); + + filesChangeNotifierService.call(); + const result = await this.waitEvent< + Result + >(executorService); + await delay(100); + if (result.isSuccess()) { + this.callStack[stackNumber].result = result.value; + this.callStack[stackNumber].hashes = filesChangeNotifierService.hashes; + + const triggerResult = await this.triggerExec( + stackLayer.process.trigger, + stackNumber + ); + triggerResult.fold( + (s) => { + s + }, + (e) => { + e; + } + ); + } + + filesChangeNotifierService.cancel(); + return; + } + public waitEvent(stream: TypedEvent): Promise { + const promise = new Promise((resolve, reject) => { + const addListener = () => { + stream.on((e) => { + const event = e as Result; + event.fold( + (s) => { + if (s.event === EXEC_EVENT.END) { + resolve(e); + } + }, + (e) => { + reject(e); + } + ); + }); + }; + addListener(); + }); + return promise; + } + private async triggerExec( + trigger: Trigger, + stackNumber: number + ): Promise> { + const hashes = this.callStack[stackNumber].hashes; + + if (hashes != null) { + return await new TriggerService(trigger, hashes, this.path).call(); + } + throw new Error("Hashes is null"); + } +} diff --git a/server/src/core/services/trigger_service.ts b/server/src/core/services/trigger_service.ts new file mode 100644 index 0000000..581d96d --- /dev/null +++ b/server/src/core/services/trigger_service.ts @@ -0,0 +1,140 @@ +import { Trigger, TriggerType } from "../model/process_model.js"; +import * as vm from "node:vm"; +import { IHashesCache } from "./files_change_notifier_service.js"; +import { EventsFileChanger } from "../model/meta_data_file_manager_model.js"; +import { Result } from "../helper/result.js"; +import { TypedEvent } from "../helper/typed_event.js"; + +export class TriggerCallResult { + results: Array; + + constructor(results: Array) { + this.results = results; + } + + get isErrorComputed(): Result { + for (const el of this.results) { + if (el instanceof TriggerErrorReport) { + return Result.error(true); + } + } + return Result.ok(false); + } +} +export class TriggerSuccessResult { + status: boolean; + processOutput: any; + trigger: string; + constructor(status: boolean, trigger: string, processOutput?: any) { + this.status = status; + this.processOutput = processOutput; + this.trigger = trigger; + } +} +export class TriggerErrorReport extends Error { + hashes: IHashesCache; + trigger: string | Trigger; + processOutput: any; + constructor( + hashes: IHashesCache, + trigger: string | Trigger, + processOutput?: any + ) { + super(); + this.hashes = hashes; + this.trigger = trigger; + this.processOutput = processOutput; + } +} +export class TriggerService extends TypedEvent { + context = {}; + + constructor(trigger: Trigger, hashes: IHashesCache, path: string) { + super(); + this.trigger = trigger; + this.hashes = hashes; + this.path = path; + this.triggerResult = null; + this.init(); + } + triggerResult: null | TriggerCallResult; + path: string; + hashes: IHashesCache; + trigger: Trigger; + private init(): void { + this.context["hashes"] = this.hashes; + } + private getAllHashesDeleteWithouts(): string[] { + return Object.entries(this.hashes).map(([k, v]) => { + if (v.event !== EventsFileChanger.delete) { + return k.replace(new RegExp(`${this.path}`), ""); + } + return ""; + }); + } + public async call(): Promise> { + if (this.trigger.type === TriggerType.PROCESS) { + const triggerResult = await this.triggerTypeProcess(); + this.emit(triggerResult); + return triggerResult.isErrorComputed; + } + + if (this.trigger.type === TriggerType.FILE) { + const triggerResult = await this.triggerTypeFile(); + this.emit(triggerResult); + return triggerResult.isErrorComputed; + } + + return Result.error(false); + } + private triggerTypeProcess(): TriggerCallResult { + const triggerResult: TriggerSuccessResult[] = []; + + for (const el of this.trigger.value) { + const processOutput = this.processCall(el); + + triggerResult.push({ + status: processOutput ? processOutput : false, + processOutput: processOutput, + trigger: el, + }); + } + return this.reportTriggerTypeProcess(triggerResult); + } + private triggerTypeFile(): TriggerCallResult { + const files = this.getAllHashesDeleteWithouts(); + + return new TriggerCallResult( + this.trigger.value.map((el) => { + let result = false; + for (const file of files) { + if (result != true) { + result = new RegExp(`${el}`).test(file); + } + } + + if (result === false) { + return new TriggerErrorReport(this.hashes, el); + } + return new TriggerSuccessResult(result, el); + }) + ); + } + private reportTriggerTypeProcess( + triggerResult: Array + ): TriggerCallResult { + return new TriggerCallResult( + triggerResult.map((el) => { + if (el.status) { + return el; + } else { + return new TriggerErrorReport(this.hashes, el.trigger); + } + }) + ); + } + private processCall(code: string): undefined | boolean | any { + const ctx = vm.createContext(this.context); + return vm.runInContext(code, ctx); + } +} diff --git a/server/test/executor_program_service_test.ts b/server/test/executor_program_service_test.ts deleted file mode 100644 index f1156e2..0000000 --- a/server/test/executor_program_service_test.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { delay } from "../src/core/helper/delay.js"; -import { EXEC_TYPE } from "../src/core/model/exec_error_model.js"; -import { ExecutorResult } from "../src/core/model/executor_result.js"; -import { ExecutorProgramService } from "../src/core/services/executor_program_service.js"; -import { TestCore } from "./core/test_core.js"; -import { resultTest as resultTest, __dirname } from "./test.js"; -import { Worker } from "node:cluster"; - - - -export class ExecutorProgramServiceTest extends ExecutorProgramService { - timeCancel = 1000; - public test = async () => { - await this.resultsTests(); - await this.longTimeCancelTest() - await this.logWriteAndEventEndTestTypeExec() - await this.logWriteAndEventEndTypeSpawn() - }; - private async logWriteAndEventEndTypeSpawn(){ - const executorProgramService = await new ExecutorProgramService(__dirname + '/') - executorProgramService.call(EXEC_TYPE.SPAWN, 'node',['./mocks/log_code.js']) - const test = TestCore.instance - let testIsOk = false - let logEvent = false - - executorProgramService.on((e) =>{ - if(e.isSuccess()) { - const executorResult = e.value as ExecutorResult - if(logEvent == false){ - logEvent = executorResult.data != null && executorResult.data != undefined - } - testIsOk = executorResult.event == 'END' && logEvent - } - }) - await delay(8000) - test.assert(testIsOk,'ExecutorProgramService EXEC_TYPE.SPAWN end event and log write') - } - private async logWriteAndEventEndTestTypeExec(){ - const executorProgramService = await new ExecutorProgramService(__dirname) - executorProgramService.call(EXEC_TYPE.EXEC, 'node ./test/mocks/log_code.js' ) - const test = TestCore.instance - executorProgramService.on((e) =>{ - if(e.isSuccess()) { - const executorResult = e.value as ExecutorResult - test.assert(executorResult.data != undefined && executorResult.event == 'END','ExecutorProgramService EXEC_TYPE.EXEC end event and log write') - } - }) - await delay(7000) - } - private async longTimeCancelTest(){ - const executorProgramService = await new ExecutorProgramService('',1000) - executorProgramService.call(EXEC_TYPE.EXEC, 'node ./test/mocks/long_code.js' ) - await delay(1500) - const worker = executorProgramService.worker as Worker - const test = TestCore.instance - test.assert(worker.isDead(),'ExecutorProgramService long time cancel') - - } - private resultsTests = async () => { - await resultTest( - new ExecutorProgramService(__dirname), - [EXEC_TYPE.EXEC, "node ./mocks/error.js"], - "ExecutorProgramService EXEC_TYPE.EXEC on Result.error", - false, - 2000 - ); - await delay(400) - await resultTest( - new ExecutorProgramService(__dirname), - [EXEC_TYPE.EXEC, "ls"], - "ExecutorProgramService EXEC_TYPE.EXEC on Result.ok", - true - ); - - await resultTest( - new ExecutorProgramService(__dirname), - [EXEC_TYPE.SPAWN, "ls"], - "ExecutorProgramService EXEC_TYPE.SPAWN on Result.ok", - true - ); - await resultTest( - new ExecutorProgramService(__dirname), - [EXEC_TYPE.SPAWN, "python3 ./mocks/s.js"], - "ExecutorProgramService EXEC_TYPE.SPAWN on Result.error", - false, - 2000 - ); - await resultTest( - new ExecutorProgramService(__dirname), - [EXEC_TYPE.SPAWN, "ls"], - "ExecutorProgramService EXEC_TYPE.SPAWN on Result.ok", - true, - 2000 - ); - }; -} diff --git a/server/test/features/executor_program_service_test.ts b/server/test/features/executor_program_service_test.ts new file mode 100644 index 0000000..9e4d106 --- /dev/null +++ b/server/test/features/executor_program_service_test.ts @@ -0,0 +1,110 @@ +import { delay } from "../../src/core/helper/delay.js"; +import { EXEC_TYPE } from "../../src/core/model/exec_error_model.js"; +import { ExecutorResult } from "../../src/core/model/executor_result.js"; +import { ExecutorProgramService } from "../../src/core/services/executor_program_service.js"; +import { TestCore } from "../core/test_core.js"; +import { resultTest as resultTest, dirname__ } from "../test.js"; +import { Worker } from "node:cluster"; + +export class ExecutorProgramServiceTest extends ExecutorProgramService { + timeCancel = 1000; + public test = async () => { + await this.resultsTests(); + await this.longTimeCancelTest(); + await this.logWriteAndEventEndTestTypeExec(); + await this.logWriteAndEventEndTypeSpawn(); + }; + private async logWriteAndEventEndTypeSpawn() { + const executorProgramService = await new ExecutorProgramService( + dirname__ + "/" + ); + executorProgramService.call(EXEC_TYPE.SPAWN, "node", [ + "./mocks/log_code.js", + ]); + const test = TestCore.instance; + let testIsOk = false; + let logEvent = false; + + executorProgramService.on((e) => { + if (e.isSuccess()) { + const executorResult = e.value as ExecutorResult; + if (logEvent == false) { + logEvent = + executorResult.data != null && executorResult.data != undefined; + } + testIsOk = executorResult.event == "END" && logEvent; + } + }); + await delay(8000); + test.assert( + testIsOk, + "ExecutorProgramService EXEC_TYPE.SPAWN end event and log write" + ); + } + private async logWriteAndEventEndTestTypeExec() { + const executorProgramService = await new ExecutorProgramService(dirname__); + executorProgramService.call( + EXEC_TYPE.EXEC, + "node ./test/mocks/log_code.js" + ); + const test = TestCore.instance; + executorProgramService.on((e) => { + if (e.isSuccess()) { + const executorResult = e.value as ExecutorResult; + test.assert( + executorResult.data != undefined && executorResult.event == "END", + "ExecutorProgramService EXEC_TYPE.EXEC end event and log write" + ); + } + }); + await delay(7000); + } + private async longTimeCancelTest() { + const executorProgramService = await new ExecutorProgramService("", 1000); + executorProgramService.call( + EXEC_TYPE.EXEC, + "node ./test/mocks/long_code.js" + ); + await delay(1500); + const worker = executorProgramService.worker as Worker; + const test = TestCore.instance; + test.assert(worker.isDead(), "ExecutorProgramService long time cancel"); + } + private resultsTests = async () => { + await resultTest( + new ExecutorProgramService(dirname__), + [EXEC_TYPE.EXEC, "node ./mocks/error.js"], + "ExecutorProgramService EXEC_TYPE.EXEC on Result.error", + false, + 4000 + ); + await delay(400); + await resultTest( + new ExecutorProgramService(dirname__), + [EXEC_TYPE.EXEC, "ls"], + "ExecutorProgramService EXEC_TYPE.EXEC on Result.ok", + true + ); + + await resultTest( + new ExecutorProgramService(dirname__), + [EXEC_TYPE.SPAWN, "ls"], + "ExecutorProgramService EXEC_TYPE.SPAWN on Result.ok", + true + ); + await resultTest( + new ExecutorProgramService(dirname__), + [EXEC_TYPE.SPAWN, "python3 ./mocks/s.js"], + "ExecutorProgramService EXEC_TYPE.SPAWN on Result.error", + false, + 2000 + ); + await resultTest( + new ExecutorProgramService(dirname__), + [EXEC_TYPE.SPAWN, "ls"], + "ExecutorProgramService EXEC_TYPE.SPAWN on Result.ok", + true, + 2000 + ); + }; +} diff --git a/server/test/files_change_notifier_service_test.ts b/server/test/features/files_change_notifier_service_test.ts similarity index 79% rename from server/test/files_change_notifier_service_test.ts rename to server/test/features/files_change_notifier_service_test.ts index 0a15b16..e47598f 100644 --- a/server/test/files_change_notifier_service_test.ts +++ b/server/test/features/files_change_notifier_service_test.ts @@ -1,11 +1,11 @@ -import { delay } from "../src/core/helper/delay.js"; -import { EventsFileChanger } from "../src/core/model/meta_data_file_manager_model.js"; -import { FilesChangeNotifierService } from "../src/core/services/files_change_notifier_service.js"; -import { assert, __dirname } from "./test.js"; import * as fs from "fs"; +import { FilesChangeNotifierService } from "../../src/core/services/files_change_notifier_service.js"; +import { EventsFileChanger } from "../../src/core/model/meta_data_file_manager_model.js"; +import { assert, dirname__ } from "../test.js"; +import { delay } from "../../src/core/helper/delay.js"; export class FilesChangerTest extends FilesChangeNotifierService { - directory = __dirname + "/context/"; + directory = dirname__ + "/context/"; data = () => { return "This is a file containing a collection"; }; @@ -68,14 +68,14 @@ export class FilesChangerTest extends FilesChangeNotifierService { this.directory = ""; const result = this.call(); assert(result.isFailure(), "Not exists directory"); - this.directory = __dirname + "/context/"; + this.directory = dirname__ + "/context/"; } public async test() { await this.createFile(); - await this.updateFile() - await this.initFile() - await this.deleteFile() - await this.notExistsDirectory() + await this.updateFile(); + await this.initFile(); + await this.deleteFile(); + await this.notExistsDirectory(); await this.testClear(); } public testClear() { diff --git a/server/test/features/stack_service_test.ts b/server/test/features/stack_service_test.ts new file mode 100644 index 0000000..1cf468f --- /dev/null +++ b/server/test/features/stack_service_test.ts @@ -0,0 +1,114 @@ +import { rmSync } from "fs"; +import * as fs from "fs"; + +import { + IssueType, + StackGenerateType, + TriggerType, +} from "../../src/core/model/process_model.js"; +import { EXEC_TYPE } from "../../src/core/model/exec_error_model.js"; +import { StackService } from "../../src/core/services/stack_service.js"; +import { delay } from "../../src/core/helper/delay.js"; +import { assert, dirname__ } from "../test.js"; + +abstract class IStackServiceTest { + abstract test(): Promise; +} + +export function readDirRecursive(path: string, filesToDir: string[] = []) { + const files = fs.readdirSync(path); + files.forEach((file) => { + let filePath = ""; + if (path[path.length - 1] !== "/") { + filePath = `${path}/${file}`; + } else { + filePath = `${path}${file}`; + } + + const stats = fs.statSync(filePath); + if (stats.isDirectory()) { + readDirRecursive(filePath, filesToDir); + } else { + filesToDir.push(file); + } + }); + return filesToDir; +} + +class SimpleTestStackServiceTest + extends StackService + implements IStackServiceTest +{ + constructor() { + super( + [ + { + process: { + type: EXEC_TYPE.EXEC, + command: `nix run gitlab:robossembler/nix-robossembler-overlay#test-script '{ + "filesMeta":[ + {"type":"folder","name":"example", "path": null,"rewrite":true} + ], + "path":"$PATH" + }'`, + isGenerating: true, + isLocaleCode: false, + issueType: IssueType.WARNING, + }, + trigger: { + type: TriggerType.FILE, + value: ["context"], + }, + env: null, + stackGenerateType: StackGenerateType.SINGLETON, + }, + { + process: { + type: EXEC_TYPE.EXEC, + command: `nix run gitlab:robossembler/nix-robossembler-overlay#test-script '{ + "filesMeta":[ + {"type":"file","name":"1.txt", "path":"example","rewrite":true} + ], + "path":"$PATH" + }'`, + isGenerating: true, + isLocaleCode: false, + issueType: IssueType.WARNING, + }, + trigger: { + type: TriggerType.FILE, + value: ["1.txt"], + }, + env: null, + stackGenerateType: StackGenerateType.SINGLETON, + }, + ], + dirname__ + "/context/" + ); + } + async test(): Promise { + await this.call(); + const testResult = readDirRecursive(this.path).equals( + ["1.txt", "test.txt"], + true + ); + await delay(100); + rmSync(this.path + "example/", { recursive: true }); + return testResult; + } +} + +export class StackServiceTest { + dirName: string; + + constructor(dirName: string) { + this.dirName = dirName; + } + public async test() { + const tests = [new SimpleTestStackServiceTest()]; + for await (const el of tests) { + assert((await el.test()) === true, el.constructor.name); + await delay(3000); + } + } +} diff --git a/server/test/features/trigger_service_test.ts b/server/test/features/trigger_service_test.ts new file mode 100644 index 0000000..1c12595 --- /dev/null +++ b/server/test/features/trigger_service_test.ts @@ -0,0 +1,137 @@ +import { + EventsFileChanger, + MetaDataFileManagerModel, +} from "../../src/core/model/meta_data_file_manager_model.js"; +import { TriggerType } from "../../src/core/model/process_model.js"; +import { TriggerService } from "../../src/core/services/trigger_service.js"; +import { assert } from "../test.js"; +abstract class TriggerTest { + abstract test(): Promise; +} +class TriggerServiceFileOkTest extends TriggerService implements TriggerTest { + constructor() { + super( + { + type: TriggerType.FILE, + value: ["context"], + }, + { + "/context/": new MetaDataFileManagerModel( + "", + "", + EventsFileChanger.create + ), + }, + "" + ); + } + async test(): Promise { + const r = await this.call(); + + return r.isSuccess(); + } +} +class TriggerServiceFileErrorTest + extends TriggerService + implements TriggerTest +{ + constructor() { + super( + { + type: TriggerType.FILE, + value: ["123"], + }, + { + "/ctx/": new MetaDataFileManagerModel("", "", EventsFileChanger.create), + "/context/": new MetaDataFileManagerModel( + "", + "", + EventsFileChanger.create + ), + }, + + "" + ); + } + async test(): Promise { + const r = await this.call(); + + return r.isFailure(); + } +} +class TriggerServiceProcessOkTest + extends TriggerService + implements TriggerTest +{ + constructor() { + super( + { + type: TriggerType.PROCESS, + value: [ + `function main(){ + return true + } + + main()`, + ], + }, + { + "/context/": new MetaDataFileManagerModel( + "", + "", + EventsFileChanger.create + ), + }, + "" + ); + } + async test(): Promise { + const r = await this.call(); + return r.isSuccess(); + } +} + +class TriggerServiceProcessErrorTest + extends TriggerService + implements TriggerTest +{ + constructor() { + super( + { + type: TriggerType.PROCESS, + value: [ + `function main(){ + return true + } + + `, + ], + }, + { + "/context/": new MetaDataFileManagerModel( + "", + "", + EventsFileChanger.create + ), + }, + "" + ); + } + async test(): Promise { + const r = await this.call(); + return r.isFailure(); + } +} +export class TriggerServiceTest { + public async test() { + const tests: TriggerTest[] = [ + new TriggerServiceFileOkTest(), + new TriggerServiceFileErrorTest(), + new TriggerServiceProcessOkTest(), + new TriggerServiceProcessErrorTest(), + ]; + for await (const el of tests) { + assert((await el.test()) === true, el.constructor.name); + } + } +} diff --git a/server/test/test.ts b/server/test/test.ts index f7886d7..0d5f165 100644 --- a/server/test/test.ts +++ b/server/test/test.ts @@ -2,46 +2,29 @@ import locator from "../src/core/di/register_di.js"; import { UnitTestEnv } from "../src/core/di/env.js"; import { fileURLToPath } from "url"; import { dirname } from "path"; -import { ExecutorProgramServiceTest } from "./executor_program_service_test.js"; -import { FilesChangerTest } from "./files_change_notifier_service_test.js"; +import { ExecutorProgramServiceTest } from "./features/executor_program_service_test.js"; +import { FilesChangerTest } from "./features/files_change_notifier_service_test.js"; import { TestCore } from "./core/test_core.js"; import "reflect-metadata"; -import { CalculationProcess, IssueType, ProcessMetaData, ProcessType, TriggerType } from "../src/core/model/process_model.js"; +import { StackServiceTest } from "./features/stack_service_test.js"; +import { TriggerServiceTest } from "./features/trigger_service_test.js"; const testCore = TestCore.instance; const __filename: string = fileURLToPath(import.meta.url); -export const __dirname: string = dirname(__filename); +export const dirname__: string = dirname(__filename); export const assert = testCore.assert; export const resultTest = testCore.resultTest; -const env = new UnitTestEnv(__dirname); +const env = new UnitTestEnv(dirname__); locator(env); const main = async () => { - await new ExecutorProgramServiceTest(__dirname).test(); - await new FilesChangerTest(__dirname).test() + await new ExecutorProgramServiceTest(dirname__).test(); + await new FilesChangerTest(dirname__).test(); + await new StackServiceTest(dirname__ + "/context/").test(); + await new TriggerServiceTest().test(); await testCore.testResult(); }; -main(); -// const logProcess:ProcessMetaData = { -// process: { -// type: ProcessType.EXEC, -// command: `nix-shell -p 'python39.withPackages(ps: with ps; [ ])' --run 'python3 p.py 1'`, -// isGenerating: false, -// isLocaleCode: false, -// issueType: IssueType.WARNING, -// timeout: 10000000, -// }, - -// trigger: { -// type: TriggerType.PROCESS, -// value: 'code', -// }, -// env: null, - -// } -// const calculationProcess = new CalculationProcess([]) - - \ No newline at end of file +main();