154 lines
9.4 KiB
JavaScript
154 lines
9.4 KiB
JavaScript
import test from 'node:test'
|
|||
|
|
import assert from 'node:assert/strict'
|
||
|
|
import { readFileSync, mkdtempSync, writeFileSync, rmSync } from 'node:fs'
|
||
|
|
import { tmpdir } from 'node:os'
|
||
|
|
import { join } from 'node:path'
|
||
|
|
import { execFileSync } from 'node:child_process'
|
||
|
|
import { planTests, verifyReports } from '../deploy/scripts/ci-test-scope.mjs'
|
||
|
|
|
||
|
|
const module = 'lastleg-service'
|
||
|
|
function java(type, body = '', set = 'main') {
|
||
|
|
const name = type.split('.').at(-1)
|
||
|
|
const pkg = type.slice(0, -name.length - 1)
|
||
|
|
return { path: `${module}/src/${set}/java/com/baiyue/lastleg/${type.replaceAll('.', '/')}.java`,
|
||
|
|
source: `package com.baiyue.lastleg.${pkg}; class ${name} { ${body} }` }
|
||
|
|
}
|
||
|
|
const source = java('dispatch.DispatchService')
|
||
|
|
const fixture = java('dispatch.Fixture', 'DispatchService service;', 'test')
|
||
|
|
const direct = java('dispatch.DispatchTest', 'Fixture fixture; @Test void check() {}', 'test')
|
||
|
|
const consumer = java('delivery.DeliveryService', 'DispatchService service;')
|
||
|
|
const indirect = java('delivery.DeliveryTest', 'DeliveryService service; @Test void check() {}', 'test')
|
||
|
|
const unrelated = java('selfoperated.AnalyticsTest', '@Test void check() {}', 'test')
|
||
|
|
const security = java('workflow.OrderSecurityTest', '@Test void check() {}', 'test')
|
||
|
|
const sources = [source, fixture, direct, consumer, indirect, unrelated, security]
|
||
|
|
const plan = (options = {}) => planTests({ modules: [module], baseline: '1234567',
|
||
|
|
changes: [source.path], sources, ...options })
|
||
|
|
|
||
|
|
test('changed Java selects transitive callers and fixture consumers without unrelated domains', () => {
|
||
|
|
const result = plan()
|
||
|
|
assert.equal(result.scope, 'AFFECTED')
|
||
|
|
assert.deepEqual(result.tests, [
|
||
|
|
'com.baiyue.lastleg.delivery.DeliveryTest', 'com.baiyue.lastleg.dispatch.DispatchTest',
|
||
|
|
'com.baiyue.lastleg.workflow.OrderSecurityTest'
|
||
|
|
])
|
||
|
|
})
|
||
|
|
|
||
|
|
test('implementation changes reach callers of interfaces, including duplicate simple names', () => {
|
||
|
|
const contract = java('dispatch.Port')
|
||
|
|
const implementation = java('dispatch.Adapter', 'implements Port')
|
||
|
|
const portCaller = java('fleet.PortCaller', 'Port port;')
|
||
|
|
const portTest = java('fleet.PortCallerTest', 'PortCaller service; @Test void check() {}', 'test')
|
||
|
|
const result = plan({ changes: [implementation.path], sources: [...sources, contract, implementation, portCaller, portTest] })
|
||
|
|
assert.ok(result.tests.includes('com.baiyue.lastleg.fleet.PortCallerTest'))
|
||
|
|
})
|
||
|
|
|
||
|
|
test('SQL tables expand to other mappers and their tests', () => {
|
||
|
|
const mapper = java('dispatch.SourceMapper', 'String sql = "UPDATE ll_dispatch_source SET version=2";')
|
||
|
|
const sibling = java('delivery.DeliveryMapper', 'String sql = "UPDATE ll_dispatch_source SET version=1";')
|
||
|
|
const check = java('delivery.DeliveryMapperTest', 'DeliveryMapper mapper; @Test void check() {}', 'test')
|
||
|
|
const result = plan({ changes: [mapper.path], sources: [...sources, mapper, sibling, check] })
|
||
|
|
assert.ok(result.tests.includes('com.baiyue.lastleg.delivery.DeliveryMapperTest'))
|
||
|
|
})
|
||
|
|
|
||
|
|
test('unchanged writes beside a changed SELECT do not pull in unrelated table consumers', () => {
|
||
|
|
const mapper = java('dispatch.QueryMapper', '@Update("UPDATE ll_shared SET value=1") void write(); @Select("SELECT value FROM ll_source") void read();')
|
||
|
|
mapper.previousSource = mapper.source.replace('SELECT value', 'SELECT old_value')
|
||
|
|
const check = java('dispatch.QueryMapperTest', 'QueryMapper mapper; @Test void check() {}', 'test')
|
||
|
|
const unrelatedTable = java('selfoperated.SharedTableTest', 'String query = "SELECT * FROM ll_shared"; @Test void check() {}', 'test')
|
||
|
|
const result = plan({ changes: [mapper.path], sources: [...sources, mapper, check, unrelatedTable] })
|
||
|
|
assert.ok(result.tests.includes('com.baiyue.lastleg.dispatch.QueryMapperTest'))
|
||
|
|
assert.ok(!result.tests.includes('com.baiyue.lastleg.selfoperated.SharedTableTest'))
|
||
|
|
mapper.source = mapper.source.replace('value=1', 'value=2')
|
||
|
|
assert.ok(plan({ changes: [mapper.path], sources: [...sources, mapper, check, unrelatedTable] })
|
||
|
|
.tests.includes('com.baiyue.lastleg.selfoperated.SharedTableTest'))
|
||
|
|
})
|
||
|
|
|
||
|
|
test('environment opt-in classes are reported separately and do not count as passing tests', () => {
|
||
|
|
const optional = java('dispatch.RealMysqlTest', 'DispatchService service; @Test void check() {}', 'test')
|
||
|
|
optional.source = optional.source.replace('class RealMysqlTest', '@EnabledIfSystemProperty(named="live",matches="true") class RealMysqlTest')
|
||
|
|
const result = plan({ sources: [...sources, optional] })
|
||
|
|
assert.ok(!result.tests.includes('com.baiyue.lastleg.dispatch.RealMysqlTest'))
|
||
|
|
assert.deepEqual(result.optInTests, ['com.baiyue.lastleg.dispatch.RealMysqlTest'])
|
||
|
|
})
|
||
|
|
|
||
|
|
test('changed test fixtures and deleted Java types keep their dependent tests', () => {
|
||
|
|
assert.ok(plan({ changes: [fixture.path] }).tests.includes('com.baiyue.lastleg.dispatch.DispatchTest'))
|
||
|
|
const deleted = java('dispatch.RemovedService')
|
||
|
|
const user = java('delivery.ReplacementTest', 'String old = "RemovedService"; @Test void check() {}', 'test')
|
||
|
|
assert.ok(plan({ changes: [deleted.path], sources: [...sources, user] }).tests.includes('com.baiyue.lastleg.delivery.ReplacementTest'))
|
||
|
|
})
|
||
|
|
|
||
|
|
test('public API changes also select the existing contract tests', () => {
|
||
|
|
const contract = java('selfoperated.AnalyticsOpenApiContractTest', '@Test void check() {}', 'test')
|
||
|
|
const result = plan({ changes: [source.path, `${module}/src/main/resources/openapi/dispatch.yaml`], sources: [...sources, contract] })
|
||
|
|
assert.ok(result.tests.includes('com.baiyue.lastleg.selfoperated.AnalyticsOpenApiContractTest'))
|
||
|
|
})
|
||
|
|
|
||
|
|
test('shared, unknown, configuration, schema and missing-baseline changes retain full regression', () => {
|
||
|
|
for (const path of ['pom.xml', 'Jenkinsfile', 'common-core/src/main/java/Core.java',
|
||
|
|
'deploy/scripts/run-ci-maven.sh', 'config/nacos/test/lastleg.yml',
|
||
|
|
`${module}/pom.xml`, `${module}/src/main/resources/db/lastleg.sql`,
|
||
|
|
`${module}/src/test/resources/test-config.yml`, `${module}/src/main/java/com/baiyue/lastleg/entity/Order.java`]) {
|
||
|
|
assert.equal(plan({ changes: [source.path, path] }).scope, 'MODULE', path)
|
||
|
|
}
|
||
|
|
assert.equal(plan({ baseline: '' }).scope, 'MODULE')
|
||
|
|
assert.equal(plan({ mode: 'MODULE' }).scope, 'MODULE')
|
||
|
|
assert.equal(plan({ modules: ['order'] }).scope, 'MODULE')
|
||
|
|
assert.equal(plan({ modules: [module, 'iam'] }).scope, 'MODULE')
|
||
|
|
assert.equal(plan({ changes: [] }).scope, 'MODULE')
|
||
|
|
assert.equal(plan({ sources: [source] }).scope, 'MODULE')
|
||
|
|
assert.throws(() => plan({ mode: 'SKIP' }))
|
||
|
|
})
|
||
|
|
|
||
|
|
test('architecture script changes run their own checks instead of broadening Java regression', () => {
|
||
|
|
const result = plan({ changes: [source.path, 'scripts/agents-index-contract.ps1',
|
||
|
|
`${module}/scripts/analyze-lastleg-dependencies.test.mjs`] })
|
||
|
|
assert.equal(result.scope, 'AFFECTED')
|
||
|
|
assert.deepEqual(result.staticChecks, [`${module}/scripts/analyze-lastleg-dependencies.test.mjs`])
|
||
|
|
})
|
||
|
|
|
||
|
|
test('focused report verification rejects missing, failed and entirely skipped classes', () => {
|
||
|
|
const focused = { scope: 'AFFECTED', tests: ['com.example.Test'] }
|
||
|
|
const report = { name: 'com.example.Test', tests: 2, skipped: 0, failures: 0, errors: 0 }
|
||
|
|
verifyReports(focused, [report])
|
||
|
|
verifyReports(focused, [{ ...report, name: 'com.example.Test$Nested' }])
|
||
|
|
for (const reports of [[], [{ ...report, tests: 0 }], [{ ...report, skipped: 2 }],
|
||
|
|
[{ ...report, failures: 1 }], [{ ...report, errors: 1 }], [{ ...report, tests: NaN }]]) {
|
||
|
|
assert.throws(() => verifyReports(focused, reports))
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
test('CLI prevents double execution of selected classes in the SQL semantic execution', () => {
|
||
|
|
const directory = mkdtempSync(join(tmpdir(), 'ci-test-scope-'))
|
||
|
|
try {
|
||
|
|
const file = join(directory, 'plan.json')
|
||
|
|
writeFileSync(file, JSON.stringify(plan()))
|
||
|
|
const args = execFileSync(process.execPath,
|
||
|
|
['deploy/scripts/ci-test-scope.mjs', 'args', '--plan', file], { encoding: 'utf8' })
|
||
|
|
assert.match(args, /-Dtest=com\.baiyue/)
|
||
|
|
assert.match(args, /-Dsurefire.failIfNoSpecifiedTests=false/)
|
||
|
|
assert.match(args, /-Dci.sql-semantic.skip=true/)
|
||
|
|
const pom = readFileSync(new URL('../lastleg-service/pom.xml', import.meta.url), 'utf8')
|
||
|
|
assert.match(pom, /<ci.sql-semantic.skip>false<\/ci.sql-semantic.skip>/)
|
||
|
|
const execution = pom.slice(pom.indexOf('<id>sql-semantic-tests</id>'))
|
||
|
|
assert.match(execution, /<skip>\$\{ci.sql-semantic.skip\}<\/skip>/)
|
||
|
|
assert.match(execution, /<reuseForks>false<\/reuseForks>/)
|
||
|
|
} finally { rmSync(directory, { recursive: true, force: true }) }
|
||
|
|
})
|
||
|
|
|
||
|
|
test('Jenkins plans from a successful baseline, verifies fresh reports and preserves module override', () => {
|
||
|
|
const pipeline = readFileSync(new URL('../Jenkinsfile', import.meta.url), 'utf8')
|
||
|
|
assert.match(pipeline, /candidate.result == 'SUCCESS' && markerIndex >= 0/)
|
||
|
|
assert.match(pipeline, /requiredServices.every \{ metadata\[1\].split\(','\).contains\(it\) \}/)
|
||
|
|
assert.match(pipeline, /if \(env.TEST_GATE_PASSED == 'true'\)/)
|
||
|
|
assert.match(pipeline, /TESTS=PASSED \$\{env.SOURCE_COMMIT\} \$\{env.SELECTED_SERVICES\}/)
|
||
|
|
assert.match(pipeline, /summary.indexOf\(' \| '\)/)
|
||
|
|
assert.match(pipeline, /env.TEST_BASE_COMMIT = previousSuccessfulTestCommit/)
|
||
|
|
assert.match(pipeline, /choice\(name: 'TEST_SCOPE', choices: \['AUTO', 'MODULE'\]/)
|
||
|
|
assert.match(pipeline, /ci-test-scope.mjs plan --modules/)
|
||
|
|
assert.match(pipeline, /ci-test-scope.mjs checks --plan/)
|
||
|
|
assert.match(pipeline, /-am test \\\$test_arguments/)
|
||
|
|
assert.match(pipeline, /ci-test-scope.mjs verify --plan/)
|
||
|
|
assert.match(pipeline, /booleanParam\(name: 'SKIP_TESTS', value: true\)/)
|
||
|
|
})
|