mirror of
https://github.com/stablyai/orca.git
synced 2026-10-01 00:02:10 +00:00
* ci: share mobile route analysis and scope mobile test runs * ci: cover mobile web runner process dependencies * test: verify mobile web selectors through the new runner --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
216 lines
7.9 KiB
JavaScript
216 lines
7.9 KiB
JavaScript
import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { dirname, join, matchesGlob, resolve } from 'node:path'
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
import { parse } from 'yaml'
|
|
import { runProcessSync } from './script-child-process.mjs'
|
|
import { shouldRunMobileReleaseChecks } from './mobile-release-check-scope.mjs'
|
|
|
|
const root = resolve(import.meta.dirname, '../..')
|
|
const workflow = parse(readFileSync(join(root, '.github/workflows/mobile.yml'), 'utf8'))
|
|
const steps = workflow.jobs.verify.steps
|
|
const detector = steps.find((step) => step.id === 'ruby-scope')
|
|
const directories = []
|
|
|
|
afterEach(() => {
|
|
for (const directory of directories.splice(0)) {
|
|
rmSync(directory, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it.each([
|
|
'src/main/runtime/rpc/dispatcher.ts',
|
|
'src/shared/rpc-contract/params.ts',
|
|
'mobile/src/session/session.test.ts',
|
|
'mobile/app/index.tsx',
|
|
'mobile/src/theme.css',
|
|
'mobile/src/session/README.md',
|
|
'mobile/docs/release.md',
|
|
'mobile/README.md',
|
|
'docs/reference/mobile.md'
|
|
])('skips Ruby checks for application/documentation-only changes: %s', (file) => {
|
|
expect(shouldRunMobileReleaseChecks([file])).toBe(false)
|
|
})
|
|
|
|
it.each([
|
|
'mobile/fastlane/Fastfile',
|
|
'mobile/fastlane/Appfile',
|
|
'mobile/fastlane/ios_release_version.rb',
|
|
'mobile/fastlane/ios_release_version_test.rb',
|
|
'mobile/Gemfile',
|
|
'mobile/Gemfile.lock',
|
|
'mobile/.ruby-version',
|
|
'mobile/.bundle/config',
|
|
'mobile/app.json',
|
|
'mobile/app.config.ts',
|
|
'mobile/ios/Podfile',
|
|
'mobile/package.json',
|
|
'mobile/pnpm-lock.yaml',
|
|
'mobile/scripts/release.mts',
|
|
'mobile/src/release.rb',
|
|
'mobile/src/release.json',
|
|
'mobile/new-toolchain/input',
|
|
'package.json',
|
|
'pnpm-lock.yaml',
|
|
'.github/workflows/mobile.yml',
|
|
'.github/workflows/mobile-ios-release.yml',
|
|
'.github/actions/install-node-dependencies/action.yml',
|
|
'config/scripts/mobile-release-check-scope.mjs',
|
|
'config/scripts/mobile-release-check-scope.test.mjs'
|
|
])('retains Ruby release coverage for changed or unknown inputs: %s', (file) => {
|
|
expect(shouldRunMobileReleaseChecks([file])).toBe(true)
|
|
expect(shouldRunMobileReleaseChecks(['mobile/src/view.tsx', file])).toBe(true)
|
|
})
|
|
|
|
it('runs Ruby checks when the changed-file evidence is empty', () => {
|
|
expect(shouldRunMobileReleaseChecks([])).toBe(true)
|
|
})
|
|
|
|
it('gates Ruby independently and retains mobile static validation', () => {
|
|
expect(steps[0].with['fetch-depth']).toBe(2)
|
|
expect(detector['working-directory']).toBe('.')
|
|
expect(steps.indexOf(detector)).toBeGreaterThan(
|
|
steps.findIndex((step) => step.uses === './.github/actions/install-node-dependencies')
|
|
)
|
|
const gated = steps.filter((step) => step.if !== undefined && step.name !== 'Test')
|
|
expect(gated.map((step) => step.name)).toEqual([
|
|
'Setup Ruby and fastlane',
|
|
'Test iOS release version resolution',
|
|
'Test TestFlight lane arguments',
|
|
'Smoke-check the Fastfile'
|
|
])
|
|
for (const step of gated) {
|
|
expect(step.if).toBe("steps.ruby-scope.outputs.should_run != 'false'")
|
|
}
|
|
for (const name of ['Typecheck', 'Typecheck tests (ratchet)', 'Lint', 'Check formatting']) {
|
|
expect(steps.find((step) => step.name === name)?.if).toBeUndefined()
|
|
expect(steps.some((step) => step.name === name)).toBe(true)
|
|
}
|
|
for (const file of [
|
|
'config/scripts/mobile-release-check-scope.mjs',
|
|
'config/scripts/mobile-release-check-scope.test.mjs',
|
|
'config/scripts/pr-code-change-scope.mjs'
|
|
]) {
|
|
expect(workflow.on.pull_request.paths.some((pattern) => matchesGlob(file, pattern))).toBe(true)
|
|
}
|
|
expect(workflow.jobs.verify.env.BUNDLE_FROZEN).toBe('true')
|
|
expect(steps.find((step) => step.name === 'Setup Ruby and fastlane').with['bundler-cache']).toBe(
|
|
true
|
|
)
|
|
expect(steps.find((step) => step.name === 'Smoke-check the Fastfile').run).toBe(
|
|
'bundle exec fastlane lanes'
|
|
)
|
|
})
|
|
|
|
function fixture(detectorStep = detector) {
|
|
const directory = mkdtempSync(join(tmpdir(), 'mobile-release-scope-'))
|
|
directories.push(directory)
|
|
const git = (...args) => {
|
|
const result = runProcessSync({ program: 'git', args, cwd: directory })
|
|
expect(result.code, result.stderr).toBe(0)
|
|
return result.stdout.trim()
|
|
}
|
|
git('init', '--quiet')
|
|
git('symbolic-ref', 'HEAD', 'refs/heads/main')
|
|
git('config', 'user.name', 'Workflow fixture')
|
|
git('config', 'user.email', 'workflow@example.invalid')
|
|
const write = (file, content) => {
|
|
mkdirSync(dirname(join(directory, file)), { recursive: true })
|
|
writeFileSync(join(directory, file), content)
|
|
}
|
|
write('mobile/src/view.tsx', 'export const view = 1\n')
|
|
write('mobile/fastlane/Fastfile', 'default_platform(:ios)\n')
|
|
mkdirSync(join(directory, 'config/scripts'), { recursive: true })
|
|
for (const file of [
|
|
'package.json',
|
|
'config/scripts/pr-code-change-scope.mjs',
|
|
'config/scripts/mobile-release-check-scope.mjs',
|
|
'config/scripts/mobile-test-change-scope.mjs'
|
|
]) {
|
|
copyFileSync(join(root, file), join(directory, file))
|
|
}
|
|
const commit = () => {
|
|
git('add', '-A')
|
|
git(
|
|
'-c',
|
|
'commit.gpgsign=false',
|
|
'-c',
|
|
'core.hooksPath=/dev/null',
|
|
'commit',
|
|
'--quiet',
|
|
'-m',
|
|
'fixture'
|
|
)
|
|
}
|
|
commit()
|
|
const detect = () => {
|
|
const output = join(directory, 'github-output')
|
|
const result = runProcessSync({
|
|
program: 'bash',
|
|
args: ['-e', '-c', detectorStep.run],
|
|
cwd: directory,
|
|
env: { ...process.env, GITHUB_OUTPUT: output, RUNNER_TEMP: directory }
|
|
})
|
|
expect(result.code, result.stderr).toBe(0)
|
|
return readFileSync(output, 'utf8')
|
|
}
|
|
return { directory, git, write, commit, detect }
|
|
}
|
|
|
|
describe.skipIf(process.platform === 'win32')('the Linux workflow detector command', () => {
|
|
it('skips Ruby after a source-only commit', () => {
|
|
const repo = fixture()
|
|
repo.write('mobile/src/view.tsx', 'export const view = 2\n')
|
|
repo.commit()
|
|
expect(repo.detect()).toBe('should_run=false\n')
|
|
})
|
|
|
|
it('keeps a deleted release input when a rename moves it into an excluded directory', () => {
|
|
const repo = fixture()
|
|
repo.git('mv', 'mobile/fastlane/Fastfile', 'mobile/src/old-release.md')
|
|
repo.commit()
|
|
expect(repo.detect()).toBe('should_run=true\n')
|
|
})
|
|
|
|
it('runs Ruby when the merge parent is unavailable', () => {
|
|
expect(fixture().detect()).toBe('should_run=true\n')
|
|
})
|
|
|
|
it('runs Ruby when the classifier cannot execute', () => {
|
|
const repo = fixture()
|
|
repo.write('mobile/src/view.tsx', 'export const view = 2\n')
|
|
repo.commit()
|
|
rmSync(join(repo.directory, 'config/scripts/mobile-release-check-scope.mjs'))
|
|
expect(repo.detect()).toBe('should_run=true\n')
|
|
})
|
|
})
|
|
|
|
describe.skipIf(process.platform === 'win32')('the mobile test detector command', () => {
|
|
const testDetector = steps.find((step) => step.id === 'test-scope')
|
|
it('skips release-only changes while retaining source changes', () => {
|
|
const repo = fixture(testDetector)
|
|
repo.write('mobile/fastlane/Fastfile', 'default_platform(:android)\n')
|
|
repo.commit()
|
|
expect(repo.detect()).toBe('should_run=false\n')
|
|
repo.write('mobile/src/view.tsx', 'export const view = 2\n')
|
|
repo.commit()
|
|
expect(repo.detect()).toBe('should_run=false\nshould_run=true\n')
|
|
})
|
|
it('retains tests after source moves into a documentation directory', () => {
|
|
const repo = fixture(testDetector)
|
|
repo.git('mv', 'mobile/src/view.tsx', 'mobile/README.md')
|
|
repo.commit()
|
|
expect(repo.detect()).toBe('should_run=true\n')
|
|
})
|
|
it('retains tests when the diff is unavailable', () => {
|
|
expect(fixture(testDetector).detect()).toBe('should_run=true\n')
|
|
})
|
|
it('retains tests when the classifier cannot execute', () => {
|
|
const repo = fixture(testDetector)
|
|
repo.write('mobile/fastlane/Fastfile', 'default_platform(:android)\n')
|
|
repo.commit()
|
|
rmSync(join(repo.directory, 'config/scripts/mobile-test-change-scope.mjs'))
|
|
expect(repo.detect()).toBe('should_run=true\n')
|
|
})
|
|
})
|