Skip to content

Commit f5a4095

Browse files
authored
Add unit tests for JVM (#61)
* Add tests for unsafe-heap.ts * Add tests for unsafe-heap.ts * Add tests for constant-pool.ts * Add tests for constant-pool.ts * Add tests for thread.ts * Add tests for AbstractClassLoader.ts and BootstrapClassLoader.ts * Add tests for threadpool.ts * Add tests for threadpool.ts
1 parent daca8ee commit f5a4095

File tree

8 files changed

+531
-1
lines changed

8 files changed

+531
-1
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { JvmObject } from '../../types/reference/Object'
2+
import AbstractSystem from '../../utils/AbstractSystem'
3+
import AbstractClassLoader, { ApplicationClassLoader } from '../../ClassLoader/AbstractClassLoader'
4+
import { TestClassLoader } from '../__utils__/test-utils'
5+
6+
describe('AbstractClassLoader', () => {
7+
test('Should properly delegate primitive class loading to the parent loader', () => {
8+
const mockParentLoader = {
9+
getPrimitiveClass: jest.fn().mockReturnValue({
10+
getName: () => 'int',
11+
checkPrimitive: () => true
12+
})
13+
}
14+
15+
const mockSystem = {} as AbstractSystem
16+
const testLoader = new TestClassLoader(mockSystem, '/test/path', mockParentLoader as unknown as AbstractClassLoader)
17+
18+
const result = testLoader.getPrimitiveClass("I")
19+
20+
expect(result.getName()).toBe("int")
21+
expect(result.checkPrimitive()).toBe(true)
22+
})
23+
24+
test('Should properly set and return the Java object representing the ApplicationClassLoader', () => {
25+
const mockParentLoader = {
26+
getPrimitiveClass: jest.fn().mockReturnValue({
27+
getName: () => 'int',
28+
checkPrimitive: () => true
29+
})
30+
}
31+
32+
const mockSystem = {} as AbstractSystem
33+
const testLoader = new ApplicationClassLoader(mockSystem, '/test/path', mockParentLoader as unknown as AbstractClassLoader)
34+
35+
expect(testLoader.getJavaObject()).toBeNull()
36+
37+
const mockJavaObject = {} as JvmObject
38+
testLoader._setJavaClassLoader(mockJavaObject)
39+
40+
expect(testLoader.getJavaObject()).toBe(mockJavaObject)
41+
})
42+
})
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// import { ErrorResult, ResultType } from '../../types/Result'
2+
import { PrimitiveClassData } from '../../types/class/ClassData'
3+
import AbstractSystem from '../../utils/AbstractSystem'
4+
import BootstrapClassLoader from '../../ClassLoader/BootstrapClassLoader'
5+
import { setupTest } from '../__utils__/test-utils'
6+
import AbstractClassLoader from '../../ClassLoader/AbstractClassLoader'
7+
8+
// let testLoader: AbstractClassLoader
9+
let bootstrapLoader: BootstrapClassLoader
10+
let mockSystem: AbstractSystem
11+
12+
beforeEach(() => {
13+
const setup = setupTest()
14+
// testLoader = setup.testLoader
15+
mockSystem = setup.testSystem
16+
bootstrapLoader = new BootstrapClassLoader(mockSystem, 'test/path')
17+
})
18+
19+
describe('BootstrapClassLoader', () => {
20+
test('Should create a BootstrapClassLoader instance with correct parameters', () => {
21+
const mockSystem = {} as AbstractSystem
22+
const classPath = 'test/path'
23+
const bootstrapLoader = new BootstrapClassLoader(mockSystem, classPath)
24+
25+
expect(bootstrapLoader).toBeInstanceOf(BootstrapClassLoader)
26+
expect(bootstrapLoader).toBeInstanceOf(AbstractClassLoader)
27+
expect(bootstrapLoader['nativeSystem']).toBe(mockSystem)
28+
expect(bootstrapLoader['classPath']).toBe(classPath)
29+
expect(bootstrapLoader['primitiveClasses']).toEqual({})
30+
})
31+
32+
// test('Should return an error result when loading an array class fails', () => {
33+
// const mockComponentClass: ClassData = {} as ClassData
34+
// const mockErrorCallback = jest.fn()
35+
// jest.spyOn(ArrayClassData.prototype, 'constructor').mockImplementation(
36+
// (_, __, ___, ____, errorCallback) => {
37+
// errorCallback({ status: ResultType.ERROR, msg: 'Mock error' })
38+
// return {} as ArrayClassData
39+
// }
40+
// )
41+
//
42+
// const result = bootstrapLoader['_loadArrayClass']('TestArray', mockComponentClass) as ErrorResult
43+
//
44+
// expect(result.status).toBe(ResultType.ERROR)
45+
// expect(result.msg).toBe('Mock error')
46+
// expect(mockErrorCallback).not.toHaveBeenCalled()
47+
// })
48+
49+
test('Should throw an error for invalid primitive class names', () => {
50+
expect(() => {
51+
bootstrapLoader.getPrimitiveClass('invalid_primitive')
52+
}).toThrow('Invalid primitive class name: invalid_primitive')
53+
54+
expect(() => {
55+
bootstrapLoader.getPrimitiveClass('Object')
56+
}).toThrow('Invalid primitive class name: Object')
57+
58+
expect(() => {
59+
bootstrapLoader.getPrimitiveClass('')
60+
}).toThrow('Invalid primitive class name: ')
61+
})
62+
63+
test('Should return a primitive class instance for valid primitive class names', () => {
64+
const primitiveClass = bootstrapLoader.getPrimitiveClass('I')
65+
66+
expect(primitiveClass).toBeInstanceOf(PrimitiveClassData)
67+
expect(primitiveClass.getDescriptor()).toBe('I')
68+
})
69+
})

src/jvm/__tests__/constant-pool.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { CONSTANT_TAG } from '../../ClassFile/constants/constants'
2+
import { ConstantInfo } from '../../ClassFile/types/constants'
3+
import { ClassData } from '../types/class/ClassData'
4+
import {
5+
// Constant,
6+
ConstantInteger,
7+
ConstantFloat,
8+
ConstantLong,
9+
ConstantDouble,
10+
ConstantUtf8,
11+
ConstantString,
12+
ConstantNameAndType,
13+
ConstantMethodType,
14+
ConstantClass,
15+
ConstantInvokeDynamic,
16+
ConstantFieldref,
17+
ConstantMethodref,
18+
ConstantInterfaceMethodref,
19+
ConstantMethodHandle
20+
} from '../types/class/Constants'
21+
// import { JvmArray } from '../types/reference/Array'
22+
import { ConstantPool } from '../constant-pool'
23+
import { setupTest } from './__utils__/test-utils'
24+
25+
// let testLoader: TestClassLoader
26+
let classData: ClassData
27+
let constantInfos: ConstantInfo[]
28+
let constantPool: ConstantPool
29+
30+
beforeEach(() => {
31+
const setup = setupTest()
32+
// testLoader = setup.testLoader
33+
classData = setup.classes.testClass
34+
constantInfos = [
35+
{ tag: CONSTANT_TAG.Integer, value: 0 },
36+
{ tag: CONSTANT_TAG.Integer, value: 42 },
37+
{ tag: CONSTANT_TAG.Float, value: 3.14 },
38+
{ tag: CONSTANT_TAG.Long, value: BigInt(1234567890) },
39+
{ tag: CONSTANT_TAG.Double, value: 2.71828 },
40+
{ tag: CONSTANT_TAG.Utf8, length: 13, value: 'Hello, World!' },
41+
{ tag: CONSTANT_TAG.String, stringIndex: 5 },
42+
{ tag: CONSTANT_TAG.NameAndType, nameIndex: 5, descriptorIndex: 5 },
43+
{ tag: CONSTANT_TAG.MethodType, descriptorIndex: 5 },
44+
{ tag: CONSTANT_TAG.Class, nameIndex: 5 },
45+
{ tag: CONSTANT_TAG.InvokeDynamic, bootstrapMethodAttrIndex: 1, nameAndTypeIndex: 7 },
46+
{ tag: CONSTANT_TAG.Fieldref, classIndex: 9, nameAndTypeIndex: 7 },
47+
{ tag: CONSTANT_TAG.Methodref, classIndex: 9, nameAndTypeIndex: 7 },
48+
{ tag: CONSTANT_TAG.InterfaceMethodref, classIndex: 9, nameAndTypeIndex: 7 },
49+
{ tag: CONSTANT_TAG.MethodHandle, referenceKind: 1, referenceIndex: 12 }
50+
]
51+
52+
constantPool = new ConstantPool(classData, constantInfos)
53+
})
54+
55+
describe('ConstantPool', () => {
56+
test('Should correctly initialize the constant pool with all provided constant types', () => {
57+
expect(constantPool.size()).toBe(15)
58+
expect(constantPool.get(1)).toBeInstanceOf(ConstantInteger)
59+
expect(constantPool.get(2)).toBeInstanceOf(ConstantFloat)
60+
expect(constantPool.get(3)).toBeInstanceOf(ConstantLong)
61+
expect(constantPool.get(4)).toBeInstanceOf(ConstantDouble)
62+
expect(constantPool.get(5)).toBeInstanceOf(ConstantUtf8)
63+
expect(constantPool.get(6)).toBeInstanceOf(ConstantString)
64+
expect(constantPool.get(7)).toBeInstanceOf(ConstantNameAndType)
65+
expect(constantPool.get(8)).toBeInstanceOf(ConstantMethodType)
66+
expect(constantPool.get(9)).toBeInstanceOf(ConstantClass)
67+
expect(constantPool.get(10)).toBeInstanceOf(ConstantInvokeDynamic)
68+
expect(constantPool.get(11)).toBeInstanceOf(ConstantFieldref)
69+
expect(constantPool.get(12)).toBeInstanceOf(ConstantMethodref)
70+
expect(constantPool.get(13)).toBeInstanceOf(ConstantInterfaceMethodref)
71+
expect(constantPool.get(14)).toBeInstanceOf(ConstantMethodHandle)
72+
73+
expect((constantPool.get(1) as ConstantInteger).get()).toBe(42)
74+
expect((constantPool.get(2) as ConstantFloat).get()).toBe(3.14)
75+
expect((constantPool.get(3) as ConstantLong).get()).toBe(BigInt(1234567890))
76+
expect((constantPool.get(4) as ConstantDouble).get()).toBe(2.71828)
77+
expect((constantPool.get(5) as ConstantUtf8).get()).toBe('Hello, World!')
78+
})
79+
80+
test('Should throw an error when get method is called with an invalid index', () => {
81+
const constantPool = new ConstantPool(classData, [
82+
{ tag: CONSTANT_TAG.Utf8, length: 5, value: 'Test' }
83+
])
84+
85+
expect(constantPool.get(-1)).toBe(undefined)
86+
expect(constantPool.get(16)).toBe(undefined)
87+
})
88+
})

src/jvm/__tests__/jni.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,14 @@ describe('JNI', () => {
5050
;(getResult as SuccessResult<any>).result(thread, [])
5151
expect(callback).toHaveBeenCalled()
5252
})
53+
54+
test('JNI: create instance with empty stdlib', () => {
55+
const testSystem = new TestSystem()
56+
const jni = new JNI('testClassPath', testSystem)
57+
58+
expect(jni).toBeInstanceOf(JNI)
59+
expect(jni['classes']).toEqual({})
60+
expect(jni['classPath']).toBe('testClassPath')
61+
expect(jni['system']).toBe(testSystem)
62+
})
5363
})

src/jvm/__tests__/thread.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { ThreadStatus } from '../constants'
2+
import { ThreadPool } from '../threadpool'
3+
import { ReferenceClassData } from '../types/class/ClassData'
4+
import { JvmObject } from '../types/reference/Object'
5+
import Thread from '../../jvm/thread'
6+
import JVM from '../../jvm/jvm'
7+
import { setupTest, TestThreadPool } from './__utils__/test-utils'
8+
9+
let thread: Thread
10+
let threadClass: ReferenceClassData
11+
// let testLoader: TestClassLoader
12+
let jvm: JVM
13+
let threadPool: ThreadPool
14+
15+
beforeEach(() => {
16+
const setup = setupTest()
17+
thread = setup.thread
18+
threadClass = setup.classes.threadClass
19+
// testLoader = setup.testLoader
20+
threadPool = new TestThreadPool(() => {})
21+
})
22+
23+
describe('Thread', () => {
24+
test('should initialize a new thread with correct default values', () => {
25+
const threadObj = {} as JvmObject
26+
27+
thread = new Thread(threadClass, jvm, threadPool, threadObj)
28+
29+
expect(thread.getStatus()).toBe(ThreadStatus.NEW)
30+
expect(thread.getFrames()).toEqual([])
31+
expect(thread.getJavaObject()).toBe(threadObj)
32+
expect(thread.getThreadPool()).toBe(threadPool)
33+
expect(thread.getJVM()).toBe(jvm)
34+
expect(thread.getThreadId()).toBe(1)
35+
expect(thread.isStackEmpty()).toBe(true)
36+
})
37+
38+
test('should correctly handle synchronized methods and monitor entering/exiting', () => {
39+
// TODO
40+
});
41+
42+
test('should properly manage thread status transitions', () => {
43+
const threadObj = new JvmObject(threadClass)
44+
thread = new Thread(threadClass, jvm, threadPool, threadObj)
45+
threadPool.updateStatus = jest.fn();
46+
47+
expect(thread.getStatus()).toBe(ThreadStatus.NEW)
48+
49+
thread.setStatus(ThreadStatus.RUNNABLE)
50+
expect(thread.getStatus()).toBe(ThreadStatus.RUNNABLE)
51+
52+
thread.setStatus(ThreadStatus.BLOCKED)
53+
expect(thread.getStatus()).toBe(ThreadStatus.BLOCKED)
54+
55+
thread.setStatus(ThreadStatus.WAITING)
56+
expect(thread.getStatus()).toBe(ThreadStatus.WAITING)
57+
58+
thread.setStatus(ThreadStatus.TIMED_WAITING)
59+
expect(thread.getStatus()).toBe(ThreadStatus.TIMED_WAITING)
60+
61+
thread.setStatus(ThreadStatus.TERMINATED)
62+
expect(thread.getStatus()).toBe(ThreadStatus.TERMINATED)
63+
64+
expect(threadPool.updateStatus).toHaveBeenCalledTimes(5)
65+
})
66+
67+
test('should manage wide (64-bit) values on the operand stack correctly', () => {
68+
// TODO
69+
})
70+
})

0 commit comments

Comments
 (0)