Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ jobs:
env:
CI: true
DD_SERVICE: datadog-ci-tests
DD_TEST_SESSION_NAME: node-${{ matrix.version }}
DD_CIVISIBILITY_AGENTLESS_ENABLED: 1
DD_API_KEY: ${{ secrets.DATADOG_API_KEY_MAIN_ACCOUNT }}
DD_APP_KEY: ${{ secrets.DATADOG_APP_KEY_MAIN_ACCOUNT }}
Expand Down Expand Up @@ -81,6 +82,7 @@ jobs:
env:
CI: true
DD_SERVICE: datadog-ci-tests
DD_TEST_SESSION_NAME: node-${{ matrix.version }}
DD_CIVISIBILITY_AGENTLESS_ENABLED: 1
DD_API_KEY: ${{ secrets.DATADOG_API_KEY_MAIN_ACCOUNT }}
DD_APP_KEY: ${{ secrets.DATADOG_APP_KEY_MAIN_ACCOUNT }}
Expand Down
15 changes: 15 additions & 0 deletions jest.config-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Forbid writing to stdout and stderr in unit tests.
// The clipanion context should always be used, and in unit tests it should be mocked with `createMockContext()`.

let spyOut
let spyErr

beforeEach(() => {
spyOut = jest.spyOn(process.stdout, 'write')
spyErr = jest.spyOn(process.stderr, 'write')
})

afterEach(() => {
expect(spyOut.mock.calls).toStrictEqual([])
expect(spyErr.mock.calls).toStrictEqual([])
})
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
module.exports = {
// Automatically clear mock calls and instances between every test
clearMocks: true,
setupFilesAfterEnv: ['<rootDir>/jest.config-setup.js'],
testMatch: ['**/__tests__/**/*.test.ts'],
transform: {
'^.+\\.tsx?$': [
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
"fast-deep-equal": "^3.1.3",
"fast-levenshtein": "^3.0.0",
"fast-xml-parser": "^4.4.1",
"form-data": "4.0.0",
"form-data": "^4.0.3",
"fuzzy": "^0.1.3",
"get-value": "^4.0.1",
"glob": "^10.4.5",
Expand Down
7 changes: 5 additions & 2 deletions src/__tests__/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {Builtins, CommandClass} from 'clipanion'
process.env.DD_BETA_COMMANDS_ENABLED = '1'

import {cli, BETA_COMMANDS} from '../cli'
import {createMockContext} from '../helpers/__tests__/testing-tools'
import {enableFips} from '../helpers/fips'

const builtins: CommandClass[] = [Builtins.HelpCommand, Builtins.VersionCommand]
Expand Down Expand Up @@ -55,6 +56,8 @@ describe('cli', () => {
const mockedEnableFips = enableFips as jest.MockedFunction<typeof enableFips>
mockedEnableFips.mockImplementation(() => true)

const context = createMockContext()

// Without the required options, the commands are not executed at all
const requiredOptions: Record<string, string[]> = {
'coverage upload': ['.', '--dry-run'],
Expand All @@ -78,7 +81,7 @@ describe('cli', () => {

test('supports the --fips option', async () => {
// When running the command with the --fips option
const exitCode = await cli.run([...command, '--fips'])
const exitCode = await cli.run([...command, '--fips'], context)

// The command calls the enableFips function with the right parameters
expect([0, 1]).toContain(exitCode)
Expand All @@ -87,7 +90,7 @@ describe('cli', () => {

test('supports the --fips-ignore-error option', async () => {
// When running the command with the --fips and --fips-ignore-error options
const exitCode = await cli.run([...command, '--fips', '--fips-ignore-error'])
const exitCode = await cli.run([...command, '--fips', '--fips-ignore-error'], context)

// The command calls the enableFips function with the right parameters
expect([0, 1]).toContain(exitCode)
Expand Down
8 changes: 7 additions & 1 deletion src/commands/elf-symbols/__tests__/elf.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import upath from 'upath'

import {createReaderFromFile} from '../../../helpers/filereader'
import {createReaderFromFile} from '../../../helpers/file-reader'

import {createUniqueTmpDirectory, deleteDirectory} from '../../dsyms/utils'

Expand All @@ -25,6 +25,7 @@ describe('elf', () => {
const getElfHeader = async (filename: string) => {
const reader = await createReaderFromFile(filename)
const elfResult = await readElfHeader(reader)
await reader.close()

return elfResult
}
Expand Down Expand Up @@ -158,6 +159,7 @@ describe('elf', () => {
const reader = await createReaderFromFile(`${fixtureDir}/dyn_aarch64`)
const elfResult = await readElfHeader(reader)
const sectionHeaders = await readElfSectionHeaderTable(reader, elfResult.elfHeader!)
await reader.close()

expect(sectionHeaders).toEqual([
{
Expand Down Expand Up @@ -559,6 +561,7 @@ describe('elf', () => {
const reader = await createReaderFromFile(`${fixtureDir}/dyn_aarch64`)
const elfResult = await readElfHeader(reader)
const programHeaders = await readElfProgramHeaderTable(reader, elfResult.elfHeader!)
await reader.close()

expect(programHeaders).toEqual([
{
Expand Down Expand Up @@ -664,6 +667,7 @@ describe('elf', () => {
const {elfHeader} = await readElfHeader(reader)
const sectionHeaders = await readElfSectionHeaderTable(reader, elfHeader!)
const {gnuBuildId, goBuildId} = await getBuildIds(reader, sectionHeaders, elfHeader!)
await reader.close()

expect(gnuBuildId).toEqual('90aef8b4a3cd45d758501e49d1d9844736c872cd')
expect(goBuildId).toBeFalsy()
Expand All @@ -674,6 +678,7 @@ describe('elf', () => {
const {elfHeader} = await readElfHeader(reader)
const sectionHeaders = await readElfSectionHeaderTable(reader, elfHeader!)
const {gnuBuildId, goBuildId} = await getBuildIds(reader, sectionHeaders, elfHeader!)
await reader.close()

expect(goBuildId).toEqual('tUhrGOwxi48kXlLhYlY3/WlmPekR2qonrFvofssLt/8beXJbt0rDaHhn3I6x8D/IA6Zd8Qc8Rsh_bFKoPVn')
expect(gnuBuildId).toBeFalsy()
Expand All @@ -684,6 +689,7 @@ describe('elf', () => {
const {elfHeader} = await readElfHeader(reader)
const sectionHeaders = await readElfSectionHeaderTable(reader, elfHeader!)
const {gnuBuildId, goBuildId} = await getBuildIds(reader, sectionHeaders, elfHeader!)
await reader.close()

expect(goBuildId).toEqual('tUhrGOwxi48kXlLhYlY3/WlmPekR2qonrFvofssLt/8beXJbt0rDaHhn3I6x8D/IA6Zd8Qc8Rsh_bFKoPVn')
expect(gnuBuildId).toEqual('6a5e565db576fe96acd8ab12bf857eb36f8afdf4')
Expand Down
32 changes: 13 additions & 19 deletions src/commands/elf-symbols/elf.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {createHash} from 'crypto'
import fs from 'fs'

import {createReadFunctions, FileReader} from '../../helpers/filereader'
import type {FileReader} from '../../helpers/file-reader'
import {createReaderFromFile, createReadFunctions} from '../../helpers/file-reader'
import {execute} from '../../helpers/utils'

import {
Expand Down Expand Up @@ -80,11 +81,6 @@ export type SectionHeader = {
sh_entsize: bigint
}

export interface Reader {
read(length: number, position?: number): Promise<Buffer>
close(): Promise<void>
}

export interface StringTable {
[index: number]: string
}
Expand Down Expand Up @@ -124,7 +120,7 @@ const getElfHeaderStart = async (filename: string): Promise<Buffer> => {
return buffer
}

export const readElfHeader = async (reader: Reader): Promise<ElfResult> => {
export const readElfHeader = async (reader: FileReader): Promise<ElfResult> => {
const result: ElfResult = {isElf: false}

try {
Expand Down Expand Up @@ -222,7 +218,7 @@ export const readElfHeader = async (reader: Reader): Promise<ElfResult> => {
}

export const readElfSectionHeader = async (
reader: Reader,
reader: FileReader,
elfHeader: ElfHeader,
index: number
): Promise<SectionHeader> => {
Expand All @@ -249,7 +245,7 @@ export const readElfSectionHeader = async (
}
}

export const readElfSectionHeaderTable = async (reader: Reader, elfHeader: ElfHeader): Promise<SectionHeader[]> => {
export const readElfSectionHeaderTable = async (reader: FileReader, elfHeader: ElfHeader): Promise<SectionHeader[]> => {
if (elfHeader.e_shnum === 0) {
return []
}
Expand Down Expand Up @@ -279,7 +275,7 @@ export const readElfSectionHeaderTable = async (reader: Reader, elfHeader: ElfHe
}

export const readElfProgramHeader = async (
reader: Reader,
reader: FileReader,
elfHeader: ElfHeader,
index: number
): Promise<ProgramHeader> => {
Expand Down Expand Up @@ -316,7 +312,7 @@ export const readElfProgramHeader = async (
}
}

export const readElfProgramHeaderTable = async (reader: Reader, elfHeader: ElfHeader): Promise<ProgramHeader[]> => {
export const readElfProgramHeaderTable = async (reader: FileReader, elfHeader: ElfHeader): Promise<ProgramHeader[]> => {
if (elfHeader.e_phnum === 0) {
return []
}
Expand All @@ -329,7 +325,7 @@ export const readElfProgramHeaderTable = async (reader: Reader, elfHeader: ElfHe
return programHeaders
}

const readElfNote = async (reader: Reader, sectionHeader: SectionHeader, elfHeader: ElfHeader) => {
const readElfNote = async (reader: FileReader, sectionHeader: SectionHeader, elfHeader: ElfHeader) => {
const buf = await reader.read(Number(sectionHeader.sh_size), Number(sectionHeader.sh_offset))
// read elf note header
const {readUInt32} = createReadFunctions(buf, elfHeader.littleEndian, elfHeader.elfClass === ElfClass.ELFCLASS32)
Expand All @@ -347,7 +343,7 @@ const readElfNote = async (reader: Reader, sectionHeader: SectionHeader, elfHead
}

export const getBuildIds = async (
reader: Reader,
reader: FileReader,
sectionHeaders: SectionHeader[],
elfHeader: ElfHeader
): Promise<{gnuBuildId: string; goBuildId: string}> => {
Expand Down Expand Up @@ -417,10 +413,10 @@ export const getElfFileMetadata = async (filename: string): Promise<ElfFileMetad
hasCode: false,
}

let fileHandle: fs.promises.FileHandle | undefined
let reader: FileReader | undefined
try {
fileHandle = await fs.promises.open(filename, 'r')
const reader = new FileReader(fileHandle)
reader = await createReaderFromFile(filename)

const {isElf, elfHeader, error} = await readElfHeader(reader)

if (isElf) {
Expand Down Expand Up @@ -457,9 +453,7 @@ export const getElfFileMetadata = async (filename: string): Promise<ElfFileMetad
} catch (error) {
metadata.error = error
} finally {
if (fileHandle) {
await fileHandle.close()
}
await reader?.close()
}

return metadata
Expand Down
15 changes: 15 additions & 0 deletions src/commands/git-metadata/__tests__/gitdb.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,18 @@ describe('gitdb', () => {
jest.spyOn(fspromises, 'mkdtemp').mockResolvedValue(tmpdir)
fs.writeFileSync(temporaryPackFile, '')
fs.writeFileSync(secondTemporaryPackFile, '')
jest.useFakeTimers()
jest.spyOn(global, 'setTimeout').mockImplementation((callback, ms) => {
jest.advanceTimersByTime(ms ?? 0)
callback()

return {} as NodeJS.Timeout
})
})

afterEach(() => {
jest.spyOn(fspromises, 'mkdtemp').mockRestore()
jest.useRealTimers()
})

afterAll(() => {
Expand Down Expand Up @@ -1217,6 +1225,8 @@ describe('gitdb', () => {
const upload = uploadToGitDB(logger, request, mocks.simpleGit as any, false)
await expect(upload).resolves.toBe(undefined)
mocks.expectCalls()
expect(global.setTimeout).toHaveBeenCalledTimes(1)
expect(global.setTimeout).toHaveBeenCalledWith(expect.any(Function), 1100)
})

test('fails after 3 http requests', async () => {
Expand Down Expand Up @@ -1322,6 +1332,9 @@ describe('gitdb', () => {
const upload = uploadToGitDB(logger, request, mocks.simpleGit as any, false)
await expect(upload).rejects.toThrow('http error')
mocks.expectCalls()
expect(global.setTimeout).toHaveBeenCalledTimes(2)
expect(global.setTimeout).toHaveBeenCalledWith(expect.any(Function), 1100)
expect(global.setTimeout).toHaveBeenCalledWith(expect.any(Function), 2200)
})

test('fail immediately if returned format is incorrect', async () => {
Expand Down Expand Up @@ -1396,6 +1409,7 @@ describe('gitdb', () => {
const upload = uploadToGitDB(logger, request, mocks.simpleGit as any, false)
await expect(upload).rejects.toThrow('Invalid commit type response')
mocks.expectCalls()
expect(global.setTimeout).toHaveBeenCalledTimes(0)
})

test('all commits are known, no packfile upload', async () => {
Expand Down Expand Up @@ -1472,5 +1486,6 @@ describe('gitdb', () => {
const upload = uploadToGitDB(logger, request, mocks.simpleGit as any, false)
await expect(upload).resolves.toBe(undefined)
mocks.expectCalls()
expect(global.setTimeout).toHaveBeenCalledTimes(0)
})
})
24 changes: 10 additions & 14 deletions src/commands/pe-symbols/pe.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import fs from 'fs'

import {FileReader, Reader} from '../../helpers/filereader'
import type {FileReader} from '../../helpers/file-reader'
import {createReaderFromFile} from '../../helpers/file-reader'

import {
CV_INFO_AGE_OFFSET,
Expand Down Expand Up @@ -96,7 +95,7 @@ const getArchitecture = (machine: number): MachineArchitecture => {
return architecture
}

const readPEHeader = async (reader: Reader): Promise<PEResult> => {
const readPEHeader = async (reader: FileReader): Promise<PEResult> => {
const result: PEResult = {
isPE: false,
}
Expand Down Expand Up @@ -173,7 +172,7 @@ type SectionHeader = {
pointerToRawData: number
}

const getSectionHeaders = async (reader: Reader, peHeader: PEHeader): Promise<SectionHeader[]> => {
const getSectionHeaders = async (reader: FileReader, peHeader: PEHeader): Promise<SectionHeader[]> => {
const sections = new Array<SectionHeader>(peHeader.numberOfSections)
const sectionHeadersOffset: number = peHeader.peHeaderOffset + peHeader.sectionHeadersOffset
const sectionsBuffer = await reader.read(peHeader.numberOfSections * IMAGE_SECTION_HEADER_SIZE, sectionHeadersOffset)
Expand Down Expand Up @@ -234,10 +233,10 @@ export const getPEFileMetadata = async (filename: string): Promise<PEFileMetadat
pdbFilename: '',
}

let fileHandle: fs.promises.FileHandle | undefined
let reader: FileReader | undefined
try {
fileHandle = await fs.promises.open(filename, 'r')
const reader = new FileReader(fileHandle)
reader = await createReaderFromFile(filename)

const peHeaderResult = await readPEHeader(reader)
if (!peHeaderResult.isPE || peHeaderResult.peHeader === undefined) {
throw peHeaderResult.error
Expand Down Expand Up @@ -265,7 +264,7 @@ export const getPEFileMetadata = async (filename: string): Promise<PEFileMetadat
return metadata
}

// The "virtual address" needs to be converted into an offset from the begining of the PE file (like peHeaderOffset).
// The "virtual address" needs to be converted into an offset from the beginning of the PE file (like peHeaderOffset).
// To be able to do that, we need to load the array of IMAGE_SECTION_HEADER that follows the optional header;
// their count is given by the IMAGE_SECTION_HEADER.NumberOfSections field
const sectionHeaders = await getSectionHeaders(reader, peHeaderResult.peHeader)
Expand All @@ -285,7 +284,7 @@ export const getPEFileMetadata = async (filename: string): Promise<PEFileMetadat
entryOffset + IMAGE_DEBUG_DIRECTORY_ADDRESSOFRAWDATA_OFFSET
)
const pdbInfoOffset: number = rvaToOffset(pdbInfoAddress, sectionHeaders)
// we are insterested in the .pdb filename that goes beyond the CV_INFO_PDB70 structure
// we are interested in the .pdb filename that goes beyond the CV_INFO_PDB70 structure
const pdbInfoBuffer = await reader.read(pdbInfoSizeOfData, pdbInfoOffset)
const pdbSignature: number = pdbInfoBuffer.readUInt32LE(CV_INFO_SIGNATURE_OFFSET)
if (pdbSignature === PDB70_SIGNATURE) {
Expand Down Expand Up @@ -329,16 +328,13 @@ export const getPEFileMetadata = async (filename: string): Promise<PEFileMetadat
entryOffset += IMAGE_DEBUG_DIRECTORY_SIZE
}
} catch (error) {
// console.log(error)
if (error instanceof Error) {
metadata.error = error
} else {
throw error
}
} finally {
if (fileHandle) {
await fileHandle.close()
}
await reader?.close()
}

return metadata
Expand Down
Loading
Loading