🏡 index : quine-global/webdriverio.git

import fsPromises from 'node:fs/promises'
import fs from 'node:fs'
import { describe, expect, it, vi, afterEach, beforeEach } from 'vitest'
import { performance } from 'node:perf_hooks'
import * as bstackLogger from '../src/bstackLogger.js'

import PerformanceTester from '../src/instrumentation/performance/performance-tester.js'
import { PERF_MEASUREMENT_ENV } from '../src/constants.js'

vi.mock('node:fs/promises', () => ({
    default: {
        createReadStream: vi.fn().mockReturnValue({ pipe: vi.fn() }),
        createWriteStream: vi.fn().mockReturnValue(
            {
                pipe: vi.fn(),
                write: vi.fn()
            }),
        stat: vi.fn().mockReturnValue(Promise.resolve({ size: 123 })),
        writeFile: vi.fn().mockReturnValue(Promise.resolve())
    }
}))

vi.mock('node:fs', () => ({
    default: {
        readFileSync: vi.fn().mockReturnValue('1234\nsomepath'),
        existsSync: vi.fn(),
        truncateSync: vi.fn(),
        mkdirSync: vi.fn()
    }
}))

vi.mock('csv-writer', () => ({
    createObjectCsvWriter: vi.fn(() => ({
        writeRecords: vi.fn().mockResolvedValue(null),
    })),
}))

const bstackLoggerSpy = vi.spyOn(bstackLogger.BStackLogger, 'logToFile')
bstackLoggerSpy.mockImplementation(() => {})

class TestClass {
    @PerformanceTester.Measure('TestMethod') // Applying the Measure decorator to this method
    method() {
        return 'method result' // A simple method to test
    }
}

describe('PerformanceTester', function () {
    afterEach(() => {
        PerformanceTester['_events'] = []
        PerformanceTester.started = false
    })

    describe('startMonitoring', function () {
        beforeEach(() => {
            process.env[PERF_MEASUREMENT_ENV] = 'true'
        })

        it('should start monitoring', () => {
            expect(PerformanceTester.started).toBe(false)
            PerformanceTester.startMonitoring('temp.csv')
            expect(PerformanceTester.started).toBe(true)
            expect(fs.mkdirSync).toBeCalledTimes(2)
        })

        it('should push to events for sync functions', async () => {
            const func = (a: number, b: number) => {
                return a + b
            }
            const timerifyFunc = performance.timerify(func)
            timerifyFunc(1, 2)
            await new Promise(resolve => setTimeout(resolve, 100))
            expect(PerformanceTester['_events']).toEqual([expect.objectContaining({ name: 'func', entryType: 'function', duration: expect.any(Number) })])
        })
    })

    describe('calculateTimes', function () {
        it('should calculate execution times correctly', () => {
            const entries = [
                { name: 'function1', duration: 100 },
                { name: 'function2', duration: 200 },
                { name: 'function1', duration: 50 },
            ]
            PerformanceTester['_events'] = (entries as any)

            const methods = ['function1', 'function2']
            const totalTime = PerformanceTester.calculateTimes(methods)
            expect(totalTime).toEqual(350)
        })
    })

    describe('stopAndGenerate', function () {
        vi.spyOn(fsPromises, 'writeFile').mockImplementation(() => Promise.resolve())

        it('should return if not started', async function () {
            await PerformanceTester.stopAndGenerate('sdf')
            expect(fsPromises.writeFile).toBeCalledTimes(0)
        })

        it('should stop and write generate HTML', async () => {
            process.env[PERF_MEASUREMENT_ENV] = 'true'
            PerformanceTester.startMonitoring('temp.csv')
            await PerformanceTester.stopAndGenerate('temp.html')
            expect(PerformanceTester.started).toBe(false)
            expect(fsPromises.writeFile).toBeCalledTimes(1)
            expect(fsPromises.writeFile).toBeCalledWith(expect.stringContaining('temp.html'), expect.anything())
        })

        it('should stop and write generate CSV', async () => {
            PerformanceTester.startMonitoring('temp.csv')
            await PerformanceTester.stopAndGenerate('temp.html')
            expect(PerformanceTester.started).toBe(false)
            expect(PerformanceTester._csvWriter.writeRecords).toBeCalledTimes(1)
        })
    })

    describe('Measure Decorator', () => {
        beforeEach(() => {
            vi.spyOn(PerformanceTester, 'measure')
        })

        afterEach(() => {
            vi.restoreAllMocks()
        })

        it('should call PerformanceTester.measure with correct arguments', () => {
            // Arrange
            const testInstance = new TestClass()
            const expectedLabel = 'TestMethod'

            // Act
            const result = testInstance.method()

            // Assert
            expect(PerformanceTester.measure).toHaveBeenCalledTimes(1)
            expect(PerformanceTester.measure).toHaveBeenCalledWith(
                expectedLabel,
                expect.any(Function),  // The original method
                expect.objectContaining({ methodName: 'method' }),
                expect.anything(),     // Arguments
                expect.anything()      // Context (this)
            )
            expect(result).toBe('method result')
        })

        it('should retain the original method\'s functionality', () => {
            // Arrange
            const testInstance = new TestClass()

            // Act
            const result = testInstance.method()

            // Assert
            expect(result).toBe('method result')
        })
    })

    describe('measureWrapper', () => {
        beforeEach(() => {
            vi.spyOn(PerformanceTester, 'getProcessId').mockReturnValue('mockedProcessId')
            PerformanceTester.browser = { sessionId: 'mockedSessionId' } as WebdriverIO.Browser
            PerformanceTester.scenarioThatRan = ['mockedScenario']
            vi.spyOn(PerformanceTester, 'measure')
        })

        afterEach(() => {
            vi.restoreAllMocks()
        })

        it('should call measure with correct arguments', () => {
            const mockFunction = vi.fn()
            const wrapper = PerformanceTester.measureWrapper('TestName', mockFunction, { command: 'testDetail' })

            wrapper('arg1', 'arg2')

            expect(PerformanceTester.measure).toHaveBeenCalledWith(
                'TestName', // name
                mockFunction, // fn
                {
                    worker: 'mockedProcessId',
                    testName: 'mockedScenario',
                    platform: 'mockedSessionId',
                    command: 'testDetail',
                }, // details
                expect.any(Array) // args
            )
        })

        it('should call the original function with correct arguments', () => {
            const mockFunction = vi.fn().mockReturnValue('result')
            const wrapper = PerformanceTester.measureWrapper('TestName', mockFunction)

            const result = wrapper('arg1', 'arg2')

            expect(mockFunction).toHaveBeenCalledWith('arg1', 'arg2')
            expect(result).toBe('result')
        })

        it('should handle empty details object', () => {
            const mockFunction = vi.fn()
            const wrapper = PerformanceTester.measureWrapper('TestName', mockFunction)

            wrapper()

            expect(PerformanceTester.measure).toHaveBeenCalledWith(
                'TestName',
                mockFunction,
                {
                    worker: 'mockedProcessId',
                    testName: 'mockedScenario',
                    platform: 'mockedSessionId',
                },
                expect.any(Array)
            )
        })
    })
})