Skip to content

Commit

Permalink
Add unit tests for JVM (#61)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
AprupKale authored Oct 29, 2024
1 parent daca8ee commit f5a4095
Show file tree
Hide file tree
Showing 8 changed files with 531 additions and 1 deletion.
42 changes: 42 additions & 0 deletions src/jvm/__tests__/ClassLoader/AbstractClassLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { JvmObject } from '../../types/reference/Object'
import AbstractSystem from '../../utils/AbstractSystem'
import AbstractClassLoader, { ApplicationClassLoader } from '../../ClassLoader/AbstractClassLoader'
import { TestClassLoader } from '../__utils__/test-utils'

describe('AbstractClassLoader', () => {
test('Should properly delegate primitive class loading to the parent loader', () => {
const mockParentLoader = {
getPrimitiveClass: jest.fn().mockReturnValue({
getName: () => 'int',
checkPrimitive: () => true
})
}

const mockSystem = {} as AbstractSystem
const testLoader = new TestClassLoader(mockSystem, '/test/path', mockParentLoader as unknown as AbstractClassLoader)

const result = testLoader.getPrimitiveClass("I")

expect(result.getName()).toBe("int")
expect(result.checkPrimitive()).toBe(true)
})

test('Should properly set and return the Java object representing the ApplicationClassLoader', () => {
const mockParentLoader = {
getPrimitiveClass: jest.fn().mockReturnValue({
getName: () => 'int',
checkPrimitive: () => true
})
}

const mockSystem = {} as AbstractSystem
const testLoader = new ApplicationClassLoader(mockSystem, '/test/path', mockParentLoader as unknown as AbstractClassLoader)

expect(testLoader.getJavaObject()).toBeNull()

const mockJavaObject = {} as JvmObject
testLoader._setJavaClassLoader(mockJavaObject)

expect(testLoader.getJavaObject()).toBe(mockJavaObject)
})
})
69 changes: 69 additions & 0 deletions src/jvm/__tests__/ClassLoader/BootstrapClassLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// import { ErrorResult, ResultType } from '../../types/Result'
import { PrimitiveClassData } from '../../types/class/ClassData'
import AbstractSystem from '../../utils/AbstractSystem'
import BootstrapClassLoader from '../../ClassLoader/BootstrapClassLoader'
import { setupTest } from '../__utils__/test-utils'
import AbstractClassLoader from '../../ClassLoader/AbstractClassLoader'

// let testLoader: AbstractClassLoader
let bootstrapLoader: BootstrapClassLoader
let mockSystem: AbstractSystem

beforeEach(() => {
const setup = setupTest()
// testLoader = setup.testLoader
mockSystem = setup.testSystem
bootstrapLoader = new BootstrapClassLoader(mockSystem, 'test/path')
})

describe('BootstrapClassLoader', () => {
test('Should create a BootstrapClassLoader instance with correct parameters', () => {
const mockSystem = {} as AbstractSystem
const classPath = 'test/path'
const bootstrapLoader = new BootstrapClassLoader(mockSystem, classPath)

expect(bootstrapLoader).toBeInstanceOf(BootstrapClassLoader)
expect(bootstrapLoader).toBeInstanceOf(AbstractClassLoader)
expect(bootstrapLoader['nativeSystem']).toBe(mockSystem)
expect(bootstrapLoader['classPath']).toBe(classPath)
expect(bootstrapLoader['primitiveClasses']).toEqual({})
})

// test('Should return an error result when loading an array class fails', () => {
// const mockComponentClass: ClassData = {} as ClassData
// const mockErrorCallback = jest.fn()
// jest.spyOn(ArrayClassData.prototype, 'constructor').mockImplementation(
// (_, __, ___, ____, errorCallback) => {
// errorCallback({ status: ResultType.ERROR, msg: 'Mock error' })
// return {} as ArrayClassData
// }
// )
//
// const result = bootstrapLoader['_loadArrayClass']('TestArray', mockComponentClass) as ErrorResult
//
// expect(result.status).toBe(ResultType.ERROR)
// expect(result.msg).toBe('Mock error')
// expect(mockErrorCallback).not.toHaveBeenCalled()
// })

test('Should throw an error for invalid primitive class names', () => {
expect(() => {
bootstrapLoader.getPrimitiveClass('invalid_primitive')
}).toThrow('Invalid primitive class name: invalid_primitive')

expect(() => {
bootstrapLoader.getPrimitiveClass('Object')
}).toThrow('Invalid primitive class name: Object')

expect(() => {
bootstrapLoader.getPrimitiveClass('')
}).toThrow('Invalid primitive class name: ')
})

test('Should return a primitive class instance for valid primitive class names', () => {
const primitiveClass = bootstrapLoader.getPrimitiveClass('I')

expect(primitiveClass).toBeInstanceOf(PrimitiveClassData)
expect(primitiveClass.getDescriptor()).toBe('I')
})
})
88 changes: 88 additions & 0 deletions src/jvm/__tests__/constant-pool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { CONSTANT_TAG } from '../../ClassFile/constants/constants'
import { ConstantInfo } from '../../ClassFile/types/constants'
import { ClassData } from '../types/class/ClassData'
import {
// Constant,
ConstantInteger,
ConstantFloat,
ConstantLong,
ConstantDouble,
ConstantUtf8,
ConstantString,
ConstantNameAndType,
ConstantMethodType,
ConstantClass,
ConstantInvokeDynamic,
ConstantFieldref,
ConstantMethodref,
ConstantInterfaceMethodref,
ConstantMethodHandle
} from '../types/class/Constants'
// import { JvmArray } from '../types/reference/Array'
import { ConstantPool } from '../constant-pool'
import { setupTest } from './__utils__/test-utils'

// let testLoader: TestClassLoader
let classData: ClassData
let constantInfos: ConstantInfo[]
let constantPool: ConstantPool

beforeEach(() => {
const setup = setupTest()
// testLoader = setup.testLoader
classData = setup.classes.testClass
constantInfos = [
{ tag: CONSTANT_TAG.Integer, value: 0 },
{ tag: CONSTANT_TAG.Integer, value: 42 },
{ tag: CONSTANT_TAG.Float, value: 3.14 },
{ tag: CONSTANT_TAG.Long, value: BigInt(1234567890) },
{ tag: CONSTANT_TAG.Double, value: 2.71828 },
{ tag: CONSTANT_TAG.Utf8, length: 13, value: 'Hello, World!' },
{ tag: CONSTANT_TAG.String, stringIndex: 5 },
{ tag: CONSTANT_TAG.NameAndType, nameIndex: 5, descriptorIndex: 5 },
{ tag: CONSTANT_TAG.MethodType, descriptorIndex: 5 },
{ tag: CONSTANT_TAG.Class, nameIndex: 5 },
{ tag: CONSTANT_TAG.InvokeDynamic, bootstrapMethodAttrIndex: 1, nameAndTypeIndex: 7 },
{ tag: CONSTANT_TAG.Fieldref, classIndex: 9, nameAndTypeIndex: 7 },
{ tag: CONSTANT_TAG.Methodref, classIndex: 9, nameAndTypeIndex: 7 },
{ tag: CONSTANT_TAG.InterfaceMethodref, classIndex: 9, nameAndTypeIndex: 7 },
{ tag: CONSTANT_TAG.MethodHandle, referenceKind: 1, referenceIndex: 12 }
]

constantPool = new ConstantPool(classData, constantInfos)
})

describe('ConstantPool', () => {
test('Should correctly initialize the constant pool with all provided constant types', () => {
expect(constantPool.size()).toBe(15)
expect(constantPool.get(1)).toBeInstanceOf(ConstantInteger)
expect(constantPool.get(2)).toBeInstanceOf(ConstantFloat)
expect(constantPool.get(3)).toBeInstanceOf(ConstantLong)
expect(constantPool.get(4)).toBeInstanceOf(ConstantDouble)
expect(constantPool.get(5)).toBeInstanceOf(ConstantUtf8)
expect(constantPool.get(6)).toBeInstanceOf(ConstantString)
expect(constantPool.get(7)).toBeInstanceOf(ConstantNameAndType)
expect(constantPool.get(8)).toBeInstanceOf(ConstantMethodType)
expect(constantPool.get(9)).toBeInstanceOf(ConstantClass)
expect(constantPool.get(10)).toBeInstanceOf(ConstantInvokeDynamic)
expect(constantPool.get(11)).toBeInstanceOf(ConstantFieldref)
expect(constantPool.get(12)).toBeInstanceOf(ConstantMethodref)
expect(constantPool.get(13)).toBeInstanceOf(ConstantInterfaceMethodref)
expect(constantPool.get(14)).toBeInstanceOf(ConstantMethodHandle)

expect((constantPool.get(1) as ConstantInteger).get()).toBe(42)
expect((constantPool.get(2) as ConstantFloat).get()).toBe(3.14)
expect((constantPool.get(3) as ConstantLong).get()).toBe(BigInt(1234567890))
expect((constantPool.get(4) as ConstantDouble).get()).toBe(2.71828)
expect((constantPool.get(5) as ConstantUtf8).get()).toBe('Hello, World!')
})

test('Should throw an error when get method is called with an invalid index', () => {
const constantPool = new ConstantPool(classData, [
{ tag: CONSTANT_TAG.Utf8, length: 5, value: 'Test' }
])

expect(constantPool.get(-1)).toBe(undefined)
expect(constantPool.get(16)).toBe(undefined)
})
})
10 changes: 10 additions & 0 deletions src/jvm/__tests__/jni.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,14 @@ describe('JNI', () => {
;(getResult as SuccessResult<any>).result(thread, [])
expect(callback).toHaveBeenCalled()
})

test('JNI: create instance with empty stdlib', () => {
const testSystem = new TestSystem()
const jni = new JNI('testClassPath', testSystem)

expect(jni).toBeInstanceOf(JNI)
expect(jni['classes']).toEqual({})
expect(jni['classPath']).toBe('testClassPath')
expect(jni['system']).toBe(testSystem)
})
})
70 changes: 70 additions & 0 deletions src/jvm/__tests__/thread.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { ThreadStatus } from '../constants'
import { ThreadPool } from '../threadpool'
import { ReferenceClassData } from '../types/class/ClassData'
import { JvmObject } from '../types/reference/Object'
import Thread from '../../jvm/thread'
import JVM from '../../jvm/jvm'
import { setupTest, TestThreadPool } from './__utils__/test-utils'

let thread: Thread
let threadClass: ReferenceClassData
// let testLoader: TestClassLoader
let jvm: JVM
let threadPool: ThreadPool

beforeEach(() => {
const setup = setupTest()
thread = setup.thread
threadClass = setup.classes.threadClass
// testLoader = setup.testLoader
threadPool = new TestThreadPool(() => {})
})

describe('Thread', () => {
test('should initialize a new thread with correct default values', () => {
const threadObj = {} as JvmObject

thread = new Thread(threadClass, jvm, threadPool, threadObj)

expect(thread.getStatus()).toBe(ThreadStatus.NEW)
expect(thread.getFrames()).toEqual([])
expect(thread.getJavaObject()).toBe(threadObj)
expect(thread.getThreadPool()).toBe(threadPool)
expect(thread.getJVM()).toBe(jvm)
expect(thread.getThreadId()).toBe(1)
expect(thread.isStackEmpty()).toBe(true)
})

test('should correctly handle synchronized methods and monitor entering/exiting', () => {
// TODO
});

test('should properly manage thread status transitions', () => {
const threadObj = new JvmObject(threadClass)
thread = new Thread(threadClass, jvm, threadPool, threadObj)
threadPool.updateStatus = jest.fn();

expect(thread.getStatus()).toBe(ThreadStatus.NEW)

thread.setStatus(ThreadStatus.RUNNABLE)
expect(thread.getStatus()).toBe(ThreadStatus.RUNNABLE)

thread.setStatus(ThreadStatus.BLOCKED)
expect(thread.getStatus()).toBe(ThreadStatus.BLOCKED)

thread.setStatus(ThreadStatus.WAITING)
expect(thread.getStatus()).toBe(ThreadStatus.WAITING)

thread.setStatus(ThreadStatus.TIMED_WAITING)
expect(thread.getStatus()).toBe(ThreadStatus.TIMED_WAITING)

thread.setStatus(ThreadStatus.TERMINATED)
expect(thread.getStatus()).toBe(ThreadStatus.TERMINATED)

expect(threadPool.updateStatus).toHaveBeenCalledTimes(5)
})

test('should manage wide (64-bit) values on the operand stack correctly', () => {
// TODO
})
})
Loading

1 comment on commit f5a4095

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report

St.
Category Percentage Covered / Total
🟡 Statements 73.95% 7176/9704
🟡 Branches 60.39% 2356/3901
🟡 Functions 69.45% 1287/1853
🟡 Lines 74.93% 6765/9028

Test suite run success

1120 tests passing in 63 suites.

Report generated by 🧪jest coverage report action from f5a4095

Please sign in to comment.