Files

86 lines
4.6 KiB
TypeScript

import { createHash } from 'node:crypto'
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { zipSync } from 'fflate'
import { expect, it } from 'vitest'
import lock from './lock.json' with { type: 'json' }
import { unpackPrimaryRuntimeWheel } from './prepare.ts'
import { prunePrimaryRuntimePythonTests } from './prune-python-tests.ts'
const records = {
numpy: `numpy-${lock.pythonPackages.numpy}.dist-info/RECORD`,
pandas: `pandas-${lock.pythonPackages.pandas}.dist-info/RECORD`,
}
it('removes only NumPy/pandas test directories and keeps retained wheel files and RECORD hashes', async () => {
const root = await mkdtemp(join(tmpdir(), 'primary-python-pruning-'))
try {
const removed = [
'numpy/tests/test_numpy.py', 'numpy/_core/tests/test_core.py', 'numpy/_core/tests/data/values,small.csv',
'numpy/testing/tests/test_utils.py', 'pandas/tests/io/test_excel.py', 'pandas/tests/io/data/workbook.xlsx',
]
const retained = [
'numpy/__init__.py', 'numpy/_core/_multiarray_tests.so', 'numpy/_core/_multiarray_tests.pyd',
'numpy/testing/__init__.py', 'numpy/_core/tests.py', 'numpy/data/values,small.csv',
'pandas/__init__.py', 'pandas/testing.py', 'pandas/_testing/__init__.py', 'pandas/_libs/testing.pyd',
`numpy-${lock.pythonPackages.numpy}.dist-info/licenses/LICENSE.txt`,
`pandas-${lock.pythonPackages.pandas}.dist-info/LICENSE`,
'other/tests/test_other.py', 'numpy_extra/tests/test_extra.py',
]
const files = Object.fromEntries([...removed, ...retained].map(path => [path, Buffer.from(`contents of ${path}\n`)]))
// Quoted paths exercise RECORD's CSV encoding even when a filename contains a comma.
const row = (path: string): string => `"${path}",sha256=${createHash('sha256').update(files[path]!).digest('base64url')},${files[path]!.length}\n`
for (const [name, record] of Object.entries(records)) {
const paths = [...removed, ...retained].filter(path => path.startsWith(`${name}/`) || path.startsWith(`${name}-`))
files[record] = Buffer.from(`${paths.map(row).join('')}${record},,\n`)
}
const archive = join(root, 'libraries.whl')
const sitePackages = join(root, 'site-packages')
const wheel = Buffer.from(zipSync(files))
await writeFile(archive, wheel)
await unpackPrimaryRuntimeWheel(archive, sitePackages)
await mkdir(join(sitePackages, 'numpy/empty/tests'), { recursive: true })
prunePrimaryRuntimePythonTests(sitePackages)
for (const path of [...removed, 'numpy/tests', 'numpy/empty/tests', 'pandas/tests']) {
await expect(readFile(join(sitePackages, path))).rejects.toMatchObject({ code: 'ENOENT' })
}
for (const path of retained) expect(await readFile(join(sitePackages, path))).toEqual(files[path])
for (const [name, record] of Object.entries(records)) {
const lines = (await readFile(join(sitePackages, record), 'utf8')).trimEnd().split('\n')
const expected = retained.filter(path => path.startsWith(`${name}/`) || path.startsWith(`${name}-`))
expect(lines).toHaveLength(expected.length + 1)
for (const path of expected) {
const expectedRow = row(path).trimEnd()
expect(lines).toContain(path.includes(',') ? expectedRow : expectedRow.replace(`"${path}"`, path))
}
expect(lines).toContain(`${record},,`)
}
expect(await readFile(archive)).toEqual(wheel)
const before = await Promise.all(Object.values(records).map(record => readFile(join(sitePackages, record))))
prunePrimaryRuntimePythonTests(sitePackages)
expect(await Promise.all(Object.values(records).map(record => readFile(join(sitePackages, record))))).toEqual(before)
} finally {
await rm(root, { recursive: true, force: true })
}
})
it.each(['"unterminated,,\n', 'numpy/tests/test_numpy.py,\n', '', ',,\n'])(
'rejects malformed installed-file records before deleting that distribution\'s tests: %j', async (record) => {
const root = await mkdtemp(join(tmpdir(), 'primary-python-record-'))
try {
await mkdir(join(root, `numpy-${lock.pythonPackages.numpy}.dist-info`))
await writeFile(join(root, records.numpy), record)
await mkdir(join(root, 'numpy/tests'), { recursive: true })
const testFile = join(root, 'numpy/tests/test_numpy.py')
await writeFile(testFile, 'test fixture\n')
expect(() => { prunePrimaryRuntimePythonTests(root) }).toThrow('invalid wheel RECORD')
expect(await readFile(testFile, 'utf8')).toBe('test fixture\n')
} finally {
await rm(root, { recursive: true, force: true })
}
},
)