Skip to content

Commit ae2c3eb

Browse files
committed
unit test cases
1 parent 38ff5c9 commit ae2c3eb

File tree

4 files changed

+116
-87
lines changed

4 files changed

+116
-87
lines changed

__tests__/code-excution/python.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,29 @@ import JobWorker from "../../src/worker";
44
import fs from 'fs'
55

66
describe("Python transform into executable", () => {
7-
const worker = new JobWorker();
7+
const functionName = "solution"
8+
const worker = new JobWorker("python3", {code: "", functionName: functionName});
89
it('should instantiate an object and execute the function', () => {
9-
const functionName = "solution"
10-
const codeContext = worker.transformCodeIntoExecutable("python3", {
11-
code: "",
12-
functionName: functionName
13-
})
10+
const codeContext = worker.transformCodeIntoExecutable()
1411
expect(codeContext).toBe(`\nprint(${mainClassName}().${functionName}())`)
1512
})
1613
})
1714

1815

1916
describe("Python code excution test", () => {
20-
const worker = new JobWorker();
2117

2218
it('should calculate a frequency in string', () => {
2319
fs.readFile(`${__dirname}/../test-code/python/counter.py`, 'utf8', (_, code) => {
2420
const codeContext: CodeContext = {
2521
code: code,
2622
functionName: "calculate"
2723
}
24+
const worker = new JobWorker("python3", codeContext);
2825
worker
29-
.startContainer("python3", codeContext)
26+
.startContainer()
3027
.then((response: ExecuteContainer) => {
3128
expect(response.codeOutput).toBe("Counter({'a': 2, 's': 2, 'w': 2, 'e': 2, 'l': 1, 'd': 1, 'k': 1})")
32-
worker.removeContainer(response.containerID!);
29+
worker.cleanupJob();
3330
})
3431
.catch(_ => {});
3532
});

__tests__/worker.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,20 @@ import JobWorker from "../src/worker";
33

44

55
describe("Docker initialization test", () => {
6-
const worker = new JobWorker();
7-
6+
87
it("should create a python container", async () => {
9-
worker.createContainer("python3").then(({ error, containerID }) => {
8+
const worker = new JobWorker("python3", {code: "", functionName: ""});
9+
worker.createContainer().then(({ error, containerID }) => {
1010
expect(error).toBe(false);
1111
expect(containerID).toBeTruthy;
12-
worker.removeContainer(containerID);
12+
worker.removeContainer();
1313
}).catch(_ => {})
1414
});
15-
15+
1616
it("should not create a container", async () => {
17+
const worker = new JobWorker("invalidName" as any, {code: "", functionName: ""});
1718
worker
18-
.createContainer("invalidName" as any)
19+
.createContainer()
1920
.catch(
2021
({ error, containerID, errorMessage }: ContainerInitialization) => {
2122
expect(error).toBe(true);

src/types/worker.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export interface WriteFileStatus {
3939
*/
4040
filePath: string;
4141

42+
4243
/**
4344
* The file format of the created file
4445
*/
@@ -53,23 +54,11 @@ export interface JobStatus extends BaseError{
5354
*/
5455
message: string;
5556

56-
57-
5857
/**
5958
* indicates if the job is retryable
6059
*/
6160
retryable: boolean;
6261

63-
/**
64-
* the raw data of the target code
65-
*/
66-
context: CodeContext;
67-
68-
/**
69-
* Newly created ontainer ID if exists
70-
*/
71-
containerID?: string;
72-
7362
/**
7463
* Newly created temp file that contain the context if exists
7564
*/
@@ -81,8 +70,5 @@ export interface ExecuteContainer extends BaseError{
8170
* stdout from the container as a result of running the code
8271
*/
8372
codeOutput?: Stdout
84-
/**
85-
* ID of the container
86-
*/
87-
containerID?: string
73+
8874
}

src/worker.ts

Lines changed: 101 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,35 @@ import {
1414
} from "./types/worker";
1515
import { containerCPULimit, containerMemLimit, mainClassName } from "./config";
1616

17+
18+
const fileFormats: Record<language, fileFormat> = {
19+
python3: "py",
20+
javascript: "js",
21+
};
22+
1723
class JobWorker {
18-
constructor() {}
24+
public language: language
25+
public codeContext: CodeContext
26+
public filename: string
27+
28+
constructor(language: language, codeContext: CodeContext) {
29+
this.language = language
30+
this.codeContext = codeContext
31+
this.filename = ""
32+
}
33+
34+
containerID: string = ""
35+
cacheFilename: string = ""
1936

20-
private _fileFormats: Record<language, fileFormat> = {
21-
python3: "py",
22-
javascript: "js",
23-
};
2437

2538
/**
2639
* Creates an appropriate docker container
2740
* to execute the target code
2841
*/
29-
createContainer(language: language): Promise<ContainerInitialization> {
42+
createContainer(): Promise<ContainerInitialization> {
3043
return new Promise((resolve, reject) => {
31-
const initCommand = `docker create ${containerMemLimit} ${containerCPULimit} ${language}`;
32-
if (!(language in this._fileFormats)) {
44+
const initCommand = `docker create ${containerMemLimit} ${containerCPULimit} ${this.language}`;
45+
if (!(this.language in fileFormats)) {
3346
return reject({
3447
error: true,
3548
errorMessage: "Invalid container name.",
@@ -48,6 +61,7 @@ class JobWorker {
4861
errorMessage: stderr,
4962
});
5063
} else {
64+
this.containerID = containerID.trim()
5165
resolve({
5266
error: false,
5367
containerID: containerID.trim(),
@@ -61,10 +75,10 @@ class JobWorker {
6175
* Returns a piece of code that executes the class method
6276
* this is language specific, so defined using switch cases
6377
*/
64-
transformCodeIntoExecutable(language: language, context: CodeContext): string {
65-
switch(language) {
78+
transformCodeIntoExecutable(): string {
79+
switch(this.language) {
6680
case "python3": {
67-
return `\nprint(${mainClassName}().${context.functionName}())`
81+
return `\nprint(${mainClassName}().${this.codeContext.functionName}())`
6882
}
6983
default: return ""
7084
}
@@ -73,24 +87,21 @@ class JobWorker {
7387
/**
7488
* writes a code into a temp file
7589
*/
76-
private async writeFile(
77-
language: language,
78-
context: CodeContext
79-
): Promise<WriteFileStatus> {
90+
private async writeFile(): Promise<WriteFileStatus> {
8091
return new Promise((resolve, reject) => {
81-
const fileName = crypto.randomBytes(32).toString("hex");
82-
const fileFormat = this._fileFormats[language];
92+
const fileFormat: string = fileFormats[this.language];
93+
this.filename = `${crypto.randomBytes(32).toString("hex")}.${fileFormat}`
8394
const filePath = path.join(
8495
__dirname,
8596
"..",
8697
"temp",
87-
`${fileName}.${fileFormat}`
98+
`${this.filename}`
8899
);
89100

90101
// appending a line to execute a specific function
91-
context.code += this.transformCodeIntoExecutable(language, context)
102+
this.codeContext.code += this.transformCodeIntoExecutable()
92103

93-
fs.writeFile(filePath, context.code, (error) => {
104+
fs.writeFile(filePath, this.codeContext.code, (error) => {
94105
if (error) {
95106
reject(error.message);
96107
} else {
@@ -100,37 +111,19 @@ class JobWorker {
100111
});
101112
}
102113

103-
/**
104-
* Removes a container with the provided containerID
105-
*/
106-
async removeContainer(containerID: string): Promise<Stdout> {
107-
return new Promise((resolve, reject) => {
108-
const removeContainer = `docker rm --force ${containerID}`;
109-
child.exec(removeContainer, (error, stdout, stderr) => {
110-
if (error) {
111-
reject(error.message);
112-
} else if (stderr) {
113-
reject(stderr);
114-
} else {
115-
resolve(stdout);
116-
}
117-
});
118-
});
119-
}
120114

121115
/**
122116
* copies the target code into the newly created
123117
* isolated container
124118
*/
125-
copyContext(
126-
containerID: string,
127-
language: language,
128-
context: CodeContext
129-
): Promise<string> {
119+
copyContext(): Promise<string> {
130120
return new Promise(async (resolve, reject) => {
131-
this.writeFile(language, context)
121+
if(!this.containerID) {
122+
reject("ContainerID not found.");
123+
}
124+
this.writeFile()
132125
.then(({ filePath, fileFormat }) => {
133-
const initCommand = `docker cp ${filePath} ${containerID}:/src/target.${fileFormat}`;
126+
const initCommand = `docker cp ${filePath} ${this.containerID}:/src/target.${fileFormat}`;
134127
child.exec(initCommand, (error, containerID, stderr) => {
135128
if (error) {
136129
reject(error.message);
@@ -152,19 +145,17 @@ class JobWorker {
152145
* 1] Creates a new container
153146
* 2] Copies the code into the contaienr
154147
*/
155-
initContainer(language: language, context: CodeContext): Promise<JobStatus> {
148+
initContainer(): Promise<JobStatus> {
156149
return new Promise(async (resolve, reject) => {
157-
this.createContainer(language)
158-
.then(async ({ containerID, error, errorMessage }) => {
150+
this.createContainer()
151+
.then(async ({ error, errorMessage }) => {
159152
if (error) return new Error(errorMessage);
160-
return this.copyContext(containerID, language, context)
153+
return this.copyContext()
161154
.then(() => {
162155
resolve({
163156
message: `Job has succedded.`,
164157
error: false,
165158
retryable: true,
166-
context: context,
167-
containerID: containerID,
168159
});
169160
})
170161
.catch((e) => {
@@ -176,7 +167,6 @@ class JobWorker {
176167
message: `Job has failed for the following reason(s): ${e}.`,
177168
jobFailed: true,
178169
retryable: true,
179-
context: context,
180170
});
181171
});
182172
});
@@ -187,12 +177,12 @@ class JobWorker {
187177
* 1] Spin up the container
188178
* 2] Record the output from the container
189179
*/
190-
startContainer(language: language, context: CodeContext): Promise<ExecuteContainer> {
180+
startContainer(): Promise<ExecuteContainer> {
191181
return new Promise((resolve, reject) => {
192-
this.initContainer(language, context)
182+
this.initContainer()
193183
.then((jobStatus: JobStatus) => {
194-
if (!jobStatus.error && jobStatus.containerID) {
195-
const startContainer = `docker start -a ${jobStatus.containerID}`;
184+
if (!jobStatus.error && this.containerID) {
185+
const startContainer = `docker start -a ${this.containerID}`;
196186
child.exec(startContainer, (error, stdout, stderr) => {
197187
if (error) {
198188
reject({
@@ -208,7 +198,6 @@ class JobWorker {
208198
resolve({
209199
error: false,
210200
codeOutput: stdout.trim(),
211-
containerID: jobStatus.containerID
212201
} as ExecuteContainer);
213202
}
214203
});
@@ -223,6 +212,62 @@ class JobWorker {
223212
.catch(_ => {});
224213
});
225214
}
215+
216+
217+
218+
/**
219+
* Removes a container with the provided containerID
220+
*/
221+
async removeContainer(): Promise<Stdout> {
222+
return new Promise((resolve, reject) => {
223+
if(!this.containerID) {
224+
reject("ContainerID not found.");
225+
}
226+
const removeContainer = `docker rm --force ${this.containerID}`;
227+
child.exec(removeContainer, (error, stdout, stderr) => {
228+
if (error) {
229+
reject(error.message);
230+
} else if (stderr) {
231+
reject(stderr);
232+
} else {
233+
resolve(stdout);
234+
}
235+
});
236+
});
237+
}
238+
239+
240+
/**
241+
* Deletes the temp file in /temp folder
242+
*/
243+
async removeCacheFile(): Promise<Stdout> {
244+
return new Promise((resolve, reject) => {
245+
if(!this.filename) {
246+
return reject("Filename not found.");
247+
}
248+
const removeContainer = `rm ${__dirname}/../temp/${this.filename}`;
249+
child.exec(removeContainer, (error, stdout, stderr) => {
250+
if (error) {
251+
reject(error.message);
252+
} else if (stderr) {
253+
reject(stderr);
254+
} else {
255+
resolve(stdout);
256+
}
257+
});
258+
});
259+
}
260+
261+
/**
262+
* Cleans up the job
263+
* Destroys the docker container & deletes cache files
264+
*/
265+
async cleanupJob(): Promise<any> {
266+
const jobs: Promise<any>[] = []
267+
jobs.push(this.removeContainer())
268+
jobs.push(this.removeCacheFile())
269+
return Promise.all(jobs)
270+
}
226271
}
227272

228273
export default JobWorker;

0 commit comments

Comments
 (0)