stack service and trigger service

This commit is contained in:
IDONTSUDO 2023-09-11 19:49:45 +03:00
parent 1ed6449ac6
commit cba12be4b1
16 changed files with 784 additions and 177 deletions

View file

@ -1,14 +1,17 @@
import { override } from "first-di"; 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"; import { MetaDataFileManagerModel } from "../model/meta_data_file_manager_model.js";
import { extensions } from "../extensions/extensions.js";
export default function locator(env: IEnv){
export default function locator(env: IEnv) {
extensions();
envRegister(env); envRegister(env);
registerRepository(env); registerRepository(env);
registerController(env); registerController(env);
registerService(env); registerService(env);
override(MetaDataFileManagerModel, MetaDataFileManagerModel) override(MetaDataFileManagerModel, MetaDataFileManagerModel);
} }
const envRegister = (env: IEnv) => { const envRegister = (env: IEnv) => {
switch (env.toStringEnv()) { switch (env.toStringEnv()) {
case UnitTestEnv.env(): case UnitTestEnv.env():
@ -19,6 +22,7 @@ const envRegister = (env: IEnv) => {
return; return;
} }
}; };
const registerRepository = (env: IEnv) => { const registerRepository = (env: IEnv) => {
switch (env.toStringEnv()) { switch (env.toStringEnv()) {
case UnitTestEnv.env(): case UnitTestEnv.env():
@ -30,6 +34,7 @@ const registerRepository = (env: IEnv) => {
return; return;
} }
}; };
const registerController = (env: IEnv) => { const registerController = (env: IEnv) => {
switch (env.toStringEnv()) { switch (env.toStringEnv()) {
case UnitTestEnv.env(): case UnitTestEnv.env():
@ -38,6 +43,7 @@ const registerController = (env: IEnv) => {
return; return;
} }
}; };
const registerService = (env: IEnv) => { const registerService = (env: IEnv) => {
switch (env.toStringEnv()) { switch (env.toStringEnv()) {
case UnitTestEnv.env(): case UnitTestEnv.env():

View file

@ -0,0 +1,31 @@
export {};
declare global {
interface Array<T> {
// @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<T>, 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;
};
}
};

View file

@ -0,0 +1,5 @@
import { ArrayEquals } from "./array.js";
export const extensions = () =>{
ArrayEquals()
}

View file

@ -9,7 +9,7 @@ export interface Disposable {
export class TypedEvent<T> { export class TypedEvent<T> {
private listeners: Listener<T>[] = []; private listeners: Listener<T>[] = [];
public listenersOncer: Listener<T>[] = []; public listenersOnces: Listener<T>[] = [];
on = (listener: Listener<T>): Disposable => { on = (listener: Listener<T>): Disposable => {
this.listeners.push(listener); this.listeners.push(listener);
@ -19,7 +19,7 @@ export class TypedEvent<T> {
}; };
once = (listener: Listener<T>): void => { once = (listener: Listener<T>): void => {
this.listenersOncer.push(listener); this.listenersOnces.push(listener);
}; };
off = (listener: Listener<T>) => { off = (listener: Listener<T>) => {
@ -31,15 +31,14 @@ export class TypedEvent<T> {
}; };
emit = (event: T) => { emit = (event: T) => {
/** Обновить слушателей */
this.listeners.forEach((listener) => this.listeners.forEach((listener) =>
listener(event) listener(event)
); );
/** Очистить очередь единожды */ if (this.listenersOnces.length > 0) {
if (this.listenersOncer.length > 0) { const toCall = this.listenersOnces;
const toCall = this.listenersOncer; this.listenersOnces = [];
this.listenersOncer = [];
toCall.forEach((listener) => listener(event)); toCall.forEach((listener) => listener(event));
} }
}; };

View file

@ -1,6 +1,6 @@
export class ExecError extends Error { export class ExecError extends Error {
static isExecError(e: any) { 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 new ExecError(e.type, e.event, e.data);
} }
return; return;
@ -8,14 +8,15 @@ export class ExecError extends Error {
script: string; script: string;
unixTime: number; unixTime: number;
type = EXEC_TYPE.EXEC; type = EXEC_TYPE.EXEC;
error:any;
constructor( constructor(
script: string, script: string,
...args: any ...args: any
) { ) {
super(...args); super(...args);
this.script = script; this.script = script;
this.unixTime = Date.now(); this.unixTime = Date.now();
this.error = args[0]
} }
} }
@ -50,10 +51,12 @@ export class SpawnError extends Error {
return; return;
} }
} }
export enum EXEC_TYPE { export enum EXEC_TYPE {
SPAWN = "SPAWN", SPAWN = "SPAWN",
EXEC = "EXEC", EXEC = "EXEC",
} }
export enum EXEC_EVENT { export enum EXEC_EVENT {
PROGRESS = "PROGRESS", PROGRESS = "PROGRESS",
ERROR = "ERROR", ERROR = "ERROR",

View file

@ -1,20 +1,18 @@
export class CalculationProcess { import { EXEC_TYPE } from "./exec_error_model.js";
process: ProcessMetaData[];
constructor(process: ProcessMetaData[]) {
this.process = process;
}
}
export interface ProcessMetaData { export interface ProcessMetaData {
process: Process; process: Process;
trigger: Trigger; trigger: Trigger;
env: Env | null; env: Env | null;
stackGenerateType:StackGenerateType;
} }
export enum ProcessType { export enum StackGenerateType{
EXEC = "EXEC", MAP = 'MAP',
SPAWN = "SPAWN", SINGLETON = 'SINGLETON'
} }
export interface Env { export interface Env {
ssh_key: string; ssh_key: string;
isUserInput: boolean; isUserInput: boolean;
@ -22,12 +20,13 @@ export interface Env {
} }
export interface Process { export interface Process {
type: ProcessType; type: EXEC_TYPE;
command: string; command: string;
isGenerating: boolean; isGenerating: boolean;
isLocaleCode: boolean; isLocaleCode: boolean;
issueType: IssueType; issueType: IssueType;
timeout: number; timeout?: number;
commit?:string | undefined;
} }
export enum IssueType { export enum IssueType {
@ -41,5 +40,5 @@ export enum TriggerType {
} }
export interface Trigger { export interface Trigger {
type: TriggerType; type: TriggerType;
value: string; value: string[];
} }

View file

@ -24,7 +24,12 @@ export class ExecutorProgramService
this.execPath = execPath; this.execPath = execPath;
this.maxTime = maxTime; this.maxTime = maxTime;
} }
private async workerExecuted(command: string, workerType: WorkerType,args:Array<string> | undefined = undefined) {
private async workerExecuted(
command: string,
workerType: WorkerType,
args: Array<string> | undefined = undefined
) {
cluster.setupPrimary({ cluster.setupPrimary({
exec: "./src/core/helper/worker_computed.js", exec: "./src/core/helper/worker_computed.js",
}); });
@ -39,22 +44,25 @@ export class ExecutorProgramService
command: command, command: command,
execPath: this.execPath, execPath: this.execPath,
type: workerType, type: workerType,
cliArgs:args cliArgs: args,
}; };
worker.send(workerDataExec); worker.send(workerDataExec);
worker.on("message", (e) => { worker.on("message", (e) => {
const spawnError = SpawnError.isError(e); const spawnError = SpawnError.isError(e);
if (spawnError instanceof SpawnError) { if (spawnError instanceof SpawnError) {
this.emit(Result.error(spawnError)); this.emit(Result.error(spawnError));
return;
} }
const executorResult = ExecutorResult.isExecutorResult(e); const executorResult = ExecutorResult.isExecutorResult(e);
if (executorResult instanceof ExecutorResult) { if (executorResult instanceof ExecutorResult) {
this.emit(Result.ok(executorResult)); this.emit(Result.ok(executorResult));
return;
} }
const execError = ExecError.isExecError(e); const execError = ExecError.isExecError(e);
if (execError instanceof ExecError) { if (execError instanceof ExecError) {
this.emit(Result.error(execError)); this.emit(Result.error(execError));
return;
} }
}); });
if (this.maxTime != null) { if (this.maxTime != null) {
@ -70,13 +78,18 @@ export class ExecutorProgramService
}, this.maxTime!); }, this.maxTime!);
} }
} }
public async call(type: EXEC_TYPE, command: string, args:Array<string> | undefined = undefined): Promise<void> {
public async call(
type: EXEC_TYPE,
command: string,
args: Array<string> | undefined = undefined
): Promise<void> {
if (type == EXEC_TYPE.EXEC) { if (type == EXEC_TYPE.EXEC) {
this.workerExecuted(command, WorkerType.EXEC); this.workerExecuted(command, WorkerType.EXEC);
return; return;
} }
this.workerExecuted(command, WorkerType.SPAWN,args); this.workerExecuted(command, WorkerType.SPAWN, args);
return; return;
} }
} }

View file

@ -4,21 +4,48 @@ import { promisify } from "node:util";
import { createHash } from "node:crypto"; import { createHash } from "node:crypto";
import "reflect-metadata"; import "reflect-metadata";
import { BinaryLike } from "crypto"; 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 { Result } from "../helper/result.js";
import { TypedEvent } from "../helper/typed_event.js"; import { TypedEvent } from "../helper/typed_event.js";
const readFileAsync = promisify(fs.readFile); const readFileAsync = promisify(fs.readFile);
const readdir = promisify(fs.readdir); const readdir = promisify(fs.readdir);
const stat = promisify(fs.stat); const stat = promisify(fs.stat);
const lsStat = promisify(fs.lstat);
function md5(content: Buffer | BinaryLike) { function joinBuffers(buffers, delimiter = " ") {
return createHash("md5").update(content).digest("hex"); const d = Buffer.from(delimiter);
return buffers.reduce((prev, b) => Buffer.concat([prev, d, b]));
} }
interface IHashesCache { async function readFileAtBuffer(path: string): Promise<Buffer> {
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<string> {
return new Promise((resolve, _reject) => {
return resolve(createHash("md5").update(content).digest("hex"));
});
}
export interface IHashesCache {
[key: string]: MetaDataFileManagerModel; [key: string]: MetaDataFileManagerModel;
} }
export abstract class IFilesChangeNotifierService { export abstract class IFilesChangeNotifierService {
abstract directory: string; abstract directory: string;
} }
@ -42,7 +69,7 @@ export class FilesChangeNotifierService
} }
async setHash(file: string) { async setHash(file: string) {
const data = await readFileAsync(file); const data = await readFileAsync(file);
const md5Current = md5(data); const md5Current = await md5(data);
this.hashes[file] = new MetaDataFileManagerModel( this.hashes[file] = new MetaDataFileManagerModel(
file, file,
@ -77,16 +104,16 @@ export class FilesChangeNotifierService
fsWait = false; fsWait = false;
}, 100); }, 100);
try { try {
const file = await readFileAsync(filePath); const file = await readFileAtBuffer(filePath);
const md5Current = md5(file); const md5Current = await md5(file);
if (md5Current === md5Previous) { if (md5Current === md5Previous) {
return; return;
} }
const status = this.hashes[filePath] === undefined const status =
this.hashes[filePath] === undefined
? EventsFileChanger.create ? EventsFileChanger.create
: EventsFileChanger.update; : EventsFileChanger.update;
const model = new MetaDataFileManagerModel( const model = new MetaDataFileManagerModel(
filePath, filePath,
md5Current, md5Current,
@ -115,8 +142,7 @@ export class FilesChangeNotifierService
cancel() { cancel() {
if (this.watcher != undefined) { if (this.watcher != undefined) {
this.watcher.close(); this.watcher.close();
this.listenersOncer = [] this.listenersOnces = [];
} }
} }
} }

View file

@ -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<string> 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<void | boolean> {
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<ExecError | SpawnError, ExecutorResult>
>(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<T>(stream: TypedEvent<T>): Promise<T> {
const promise = new Promise<T>((resolve, reject) => {
const addListener = () => {
stream.on((e) => {
const event = e as Result<ExecError | SpawnError, ExecutorResult>;
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<Result<boolean, boolean>> {
const hashes = this.callStack[stackNumber].hashes;
if (hashes != null) {
return await new TriggerService(trigger, hashes, this.path).call();
}
throw new Error("Hashes is null");
}
}

View file

@ -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<TriggerSuccessResult | TriggerErrorReport>;
constructor(results: Array<TriggerSuccessResult | TriggerErrorReport>) {
this.results = results;
}
get isErrorComputed(): Result<boolean, boolean> {
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<TriggerCallResult> {
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<Result<boolean, boolean>> {
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<TriggerSuccessResult>
): 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);
}
}

View file

@ -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
);
};
}

View file

@ -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
);
};
}

View file

@ -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 * 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 { export class FilesChangerTest extends FilesChangeNotifierService {
directory = __dirname + "/context/"; directory = dirname__ + "/context/";
data = () => { data = () => {
return "This is a file containing a collection"; return "This is a file containing a collection";
}; };
@ -68,14 +68,14 @@ export class FilesChangerTest extends FilesChangeNotifierService {
this.directory = ""; this.directory = "";
const result = this.call(); const result = this.call();
assert(result.isFailure(), "Not exists directory"); assert(result.isFailure(), "Not exists directory");
this.directory = __dirname + "/context/"; this.directory = dirname__ + "/context/";
} }
public async test() { public async test() {
await this.createFile(); await this.createFile();
await this.updateFile() await this.updateFile();
await this.initFile() await this.initFile();
await this.deleteFile() await this.deleteFile();
await this.notExistsDirectory() await this.notExistsDirectory();
await this.testClear(); await this.testClear();
} }
public testClear() { public testClear() {

View file

@ -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<boolean>;
}
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<boolean> {
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);
}
}
}

View file

@ -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<boolean>;
}
class TriggerServiceFileOkTest extends TriggerService implements TriggerTest {
constructor() {
super(
{
type: TriggerType.FILE,
value: ["context"],
},
{
"/context/": new MetaDataFileManagerModel(
"",
"",
EventsFileChanger.create
),
},
""
);
}
async test(): Promise<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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);
}
}
}

View file

@ -2,46 +2,29 @@ import locator from "../src/core/di/register_di.js";
import { UnitTestEnv } from "../src/core/di/env.js"; import { UnitTestEnv } from "../src/core/di/env.js";
import { fileURLToPath } from "url"; import { fileURLToPath } from "url";
import { dirname } from "path"; import { dirname } from "path";
import { ExecutorProgramServiceTest } from "./executor_program_service_test.js"; import { ExecutorProgramServiceTest } from "./features/executor_program_service_test.js";
import { FilesChangerTest } from "./files_change_notifier_service_test.js"; import { FilesChangerTest } from "./features/files_change_notifier_service_test.js";
import { TestCore } from "./core/test_core.js"; import { TestCore } from "./core/test_core.js";
import "reflect-metadata"; 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 testCore = TestCore.instance;
const __filename: string = fileURLToPath(import.meta.url); 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 assert = testCore.assert;
export const resultTest = testCore.resultTest; export const resultTest = testCore.resultTest;
const env = new UnitTestEnv(__dirname); const env = new UnitTestEnv(dirname__);
locator(env); locator(env);
const main = async () => { const main = async () => {
await new ExecutorProgramServiceTest(__dirname).test(); await new ExecutorProgramServiceTest(dirname__).test();
await new FilesChangerTest(__dirname).test() await new FilesChangerTest(dirname__).test();
await new StackServiceTest(dirname__ + "/context/").test();
await new TriggerServiceTest().test();
await testCore.testResult(); await testCore.testResult();
}; };
main(); 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([])