|
|
@@ -0,0 +1,109 @@
|
|
|
+import { readFileSync } from 'node:fs'
|
|
|
+import { resolve } from 'node:path'
|
|
|
+import { runInNewContext } from 'node:vm'
|
|
|
+import * as yaml from 'js-yaml'
|
|
|
+import { describe, expect, it } from 'vitest'
|
|
|
+
|
|
|
+const root = resolve(import.meta.dirname, '..')
|
|
|
+const workflow = yaml.load(readFileSync(resolve(root, '.github/workflows/build-exe-for-python-sdk.yml'), 'utf8')) as {
|
|
|
+ jobs: Record<string, { 'runs-on': string; steps: Array<{ name?: string; id?: string; uses?: string; if?: string; shell?: string; run?: string; with?: Record<string, unknown> }> }>
|
|
|
+}
|
|
|
+const build = workflow.jobs.build!
|
|
|
+const selector = build['runs-on'].slice(3, -2).trim()
|
|
|
+const windows = ['self-hosted', 'dsh-win-ci', 'windows', 'x64']
|
|
|
+
|
|
|
+function context() {
|
|
|
+ return {
|
|
|
+ inputs: { ci: true, release: false },
|
|
|
+ github: {
|
|
|
+ repository: 'deepseek-harness/deepseek-harness',
|
|
|
+ event_name: 'pull_request',
|
|
|
+ ref: 'refs/pull/42/merge',
|
|
|
+ event: { pull_request: {
|
|
|
+ head: { repo: { full_name: 'deepseek-harness/deepseek-harness', fork: false } },
|
|
|
+ user: { login: 'contributor' },
|
|
|
+ } },
|
|
|
+ },
|
|
|
+ matrix: { target: 'node24-win-x64', runner: 'windows-2025' },
|
|
|
+ vars: { DSH_CI_FAILOVER_WINDOWS: 'selfhosted' },
|
|
|
+ fromJSON: JSON.parse,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function route(value: ReturnType<typeof context>, expression = selector): unknown {
|
|
|
+ // The workflow uses only comparisons, booleans and fromJSON; execute that exact expression.
|
|
|
+ return runInNewContext(expression, value, { timeout: 1000 })
|
|
|
+}
|
|
|
+
|
|
|
+describe('Python runtime self-hosted routing', () => {
|
|
|
+ it('routes same-repository member PRs and master CI to native x64 Windows', () => {
|
|
|
+ expect(route(context())).toEqual(windows)
|
|
|
+ const master = context()
|
|
|
+ master.github.event_name = 'push'
|
|
|
+ master.github.ref = 'refs/heads/master'
|
|
|
+ expect(route(master)).toEqual(windows)
|
|
|
+ })
|
|
|
+
|
|
|
+ it.each([
|
|
|
+ ['release caller', (value: ReturnType<typeof context>) => { value.inputs.release = true }],
|
|
|
+ ['non-CI caller', (value: ReturnType<typeof context>) => { value.inputs.ci = false }],
|
|
|
+ ['manual dispatch', (value: ReturnType<typeof context>) => { value.github.event_name = 'workflow_dispatch' }],
|
|
|
+ ['pull_request_target', (value: ReturnType<typeof context>) => { value.github.event_name = 'pull_request_target' }],
|
|
|
+ ['unknown event', (value: ReturnType<typeof context>) => { value.github.event_name = '' }],
|
|
|
+ ['fork', (value: ReturnType<typeof context>) => { value.github.event.pull_request.head.repo.fork = true }],
|
|
|
+ ['different repository head', (value: ReturnType<typeof context>) => { value.github.event.pull_request.head.repo.full_name = 'someone/fork' }],
|
|
|
+ ['different caller repository', (value: ReturnType<typeof context>) => { value.github.repository = 'someone/fork' }],
|
|
|
+ ['Dependabot author', (value: ReturnType<typeof context>) => { value.github.event.pull_request.user.login = 'dependabot[bot]' }],
|
|
|
+ ['disabled failover', (value: ReturnType<typeof context>) => { value.vars.DSH_CI_FAILOVER_WINDOWS = '' }],
|
|
|
+ ['unknown failover value', (value: ReturnType<typeof context>) => { value.vars.DSH_CI_FAILOVER_WINDOWS = 'hosted' }],
|
|
|
+ ['branch push', (value: ReturnType<typeof context>) => { value.github.event_name = 'push'; value.github.ref = 'refs/heads/topic' }],
|
|
|
+ ['tag push', (value: ReturnType<typeof context>) => { value.github.event_name = 'push'; value.github.ref = 'refs/tags/python-v1' }],
|
|
|
+ ] as const)('keeps %s on the hosted fallback', (_name, change) => {
|
|
|
+ const value = context()
|
|
|
+ change(value)
|
|
|
+ expect(route(value)).toBe('windows-2025')
|
|
|
+ })
|
|
|
+
|
|
|
+ it.each([
|
|
|
+ ['node24-linux-x64', 'ubuntu-latest'],
|
|
|
+ ['node24-linux-arm64', 'ubuntu-24.04-arm'],
|
|
|
+ ['node24-macos-arm64', 'macos-latest'],
|
|
|
+ ['node24-macos-x64', 'macos-15-intel'],
|
|
|
+ ])('keeps %s hosted even with failover enabled', (target, runner) => {
|
|
|
+ const value = context()
|
|
|
+ value.matrix = { target, runner }
|
|
|
+ expect(route(value)).toBe(runner)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('keeps setup helper jobs on hosted images', () => {
|
|
|
+ expect(workflow.jobs.plan!['runs-on']).toBe('ubuntu-latest')
|
|
|
+ expect(workflow.jobs['sdk-wheel']!['runs-on']).toBe('ubuntu-latest')
|
|
|
+ })
|
|
|
+
|
|
|
+ it('isolates setup before pnpm and excludes shared installers and cache archives', () => {
|
|
|
+ const privateSetup = build.steps.findIndex(step => step.id === 'private-windows')
|
|
|
+ expect(privateSetup).toBeGreaterThan(0)
|
|
|
+ expect(privateSetup).toBeLessThan(build.steps.findIndex(step => step.uses?.startsWith('pnpm/action-setup@')))
|
|
|
+ for (const step of build.steps.filter(step => step.uses?.startsWith('actions/setup-python@') || step.uses?.startsWith('actions/cache@') || step.name === 'Install Python build tooling')) {
|
|
|
+ expect(step.if).toBe("runner.environment != 'self-hosted'")
|
|
|
+ }
|
|
|
+ expect(build.steps.find(step => step.name?.startsWith('Enable Windows'))?.if).toBe("runner.os == 'Windows' && runner.environment != 'self-hosted'")
|
|
|
+ expect(build.steps.find(step => step.uses?.startsWith('actions/setup-node@'))?.with?.cache).toContain("runner.environment != 'self-hosted'")
|
|
|
+ expect(build.steps.at(-1)).toMatchObject({ if: "always() && steps.private-windows.outputs.root != ''", shell: 'pwsh' })
|
|
|
+ const cleanup = build.steps.at(-1)!.run!
|
|
|
+ expect(cleanup).toContain('"NODE_COMPILE_CACHE=" >> $env:GITHUB_ENV')
|
|
|
+ expect(cleanup).toContain('"TMP=$env:RUNNER_TEMP" >> $env:GITHUB_ENV')
|
|
|
+ expect(cleanup).toContain('"TEMP=$env:RUNNER_TEMP" >> $env:GITHUB_ENV')
|
|
|
+ expect(cleanup).toContain('maxRetries: 10, retryDelay: 100')
|
|
|
+ })
|
|
|
+
|
|
|
+ it('pins portable Python without registry or shared cache writes', () => {
|
|
|
+ const setup = readFileSync(resolve(root, 'scripts/setup-python-runtime-windows.ps1'), 'utf8')
|
|
|
+ expect(setup).toContain('--no-bin --no-registry 3.10')
|
|
|
+ expect(setup).toContain('--managed-python --no-python-downloads --seed')
|
|
|
+ expect(setup).toContain('UV_PYTHON_INSTALL_REGISTRY')
|
|
|
+ expect(setup).toContain('PNPM_CONFIG_STORE_DIR')
|
|
|
+ expect(setup).toContain('PKG_CACHE_PATH')
|
|
|
+ expect(setup).not.toMatch(/reg add|Set-ItemProperty|InstallAllUsers/)
|
|
|
+ })
|
|
|
+})
|