Files
qwen-code/scripts/tests/spotbugs-gate.test.js
易良andQwen-Coder a4562edb67 fix(ci): align Java integration database and lock lifetime (#14936)
* fix(ci): align Java integration database and lock lifetime

* test(ci): re-pin the sdk-java lock and MySQL probe after the single-step merge

The merge commit staged the conflict resolution before these two re-pins were
written, so they land as a follow-up in the same push:

- `keeps the host lock out of every Maven child` counted three lock-bearing
  steps; #14903 collapsed the MariaDB lane's two Maven steps into one, so the
  count is now two (test job + mysql-integration).
- The MySQL startup probe took the step's first `mvn` line, which is now the
  `-DskipTests install` staging command; select the `-Pmysql-integration`
  command instead so the probe still exercises the consumer.

Verified: vitest sdk-java-workflow.test.js 96 passed; prettier --check clean.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-conflict/jmvgz448zlp

* fix(ci): skip the MySQL binlog, drop the unused database, correct the reaper bound

Three independent corrections to the mysql-integration lane this PR reworks.

1. MySQL 8 enables the binary log by default (MariaDB 10.11 did not in the
   configuration this lane replaced), and the datadir is an uncapped tmpfs.
   Nothing in packages/managed-agent-server/src or packages/sdk-java reads a
   binlog (0 hits for binlog|log_bin|GTID|SHOW MASTER STATUS), and the
   container is disposable with no replica, so `--skip-log-bin` is appended
   after the image name beside --innodb-use-native-aio=0. Without it the
   ~67 MiB hex-encoded LONGBLOB query this lane already raises
   max_allowed_packet for is serialized a second time into host RAM.

2. `--env MYSQL_DATABASE='runtime_broker_test'` had no consumer: the lane's
   only connection is -Dmysql.url=.../managed_agent_test?createDatabaseIfNotExist=true.
   The declaration invited a cleanup that drops createDatabaseIfNotExist and
   dies at first connect with Unknown database. Dropping it lets the JDBC URL
   own the schema and shrinks the diff.

3. The cleanup comment and removal-failure warning said the host reaper
   reclaims a stranded container "after a 24h age gate" / "up to a day".
   The real bound is ~48h: qwen-docker-cleanup.sh:31 gates on a strict
   `now - created_at > 86400` (exactly 24h is SKIPPED) and
   qwen-docker-cleanup.timer fires once a day, so a container created just
   after a fire is skipped at the next one and reaped at the one after.

Test pins updated in the same change: the start-step assertion, the exact
docker-run trace literal, and the cleanup warning literal. All three were
mutant-verified red (drop --skip-log-bin / re-add MYSQL_DATABASE / restore
"after 24h") and restored byte-identical.

Only this PR's own copy of the reaper wording is corrected; the two
pre-existing copies (sdk-java.yml's event-transport-multi-node job and
release-managed-agent-reference.yml) are untouched to keep this diff scoped.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvhb9cysmc

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-21 14:39:26 +00:00

102 lines
3.9 KiB
JavaScript

/**
* @license
* Copyright 2026 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import { readFileSync } from 'node:fs';
import { parse } from 'yaml';
import { describe, expect, it } from 'vitest';
const read = (file) =>
readFileSync(new URL(`../../${file}`, import.meta.url), 'utf8');
const GATED_MODULES = ['packages/managed-agent-server'];
describe('SpotBugs gate witness', () => {
// A Match whose only predicate is a wildcard class or package turns the
// gate into a silent no-op: the build reports the same `BugInstance size
// is 0` as a genuinely clean tree.
it('rejects wildcard-only Match blocks in the per-module excludes files', () => {
for (const mod of GATED_MODULES) {
const xml = read(`${mod}/spotbugs-excludes.xml`);
for (const [, body] of xml.matchAll(/<Match>([\s\S]*?)<\/Match>/g)) {
const specific =
/<Bug\s+pattern="[^"]+"/.test(body) ||
/<Method[\s/>]/.test(body) ||
/<Field[\s/>]/.test(body) ||
/<Class\s+name="(?!~)/.test(body) ||
/<Package\s+name="(?!~)/.test(body);
expect(
specific,
`${mod}/spotbugs-excludes.xml: every <Match> ` +
'needs a Bug pattern, a Method/Field, or a concrete (non-~) ' +
'Class/Package name — wildcard-only blocks silence the gate',
).toBe(true);
}
}
});
// The combined Server verification checks both Server and Broker classes.
// The SDK is now its only staged dependency.
it('keeps the gate on combined Server verification without staging a Broker artifact', () => {
const workflow = parse(read('.github/workflows/sdk-java.yml'));
const steps = Object.values(workflow.jobs).flatMap(
(job) => job.steps ?? [],
);
const byName = (name) => steps.filter((step) => step.name === name);
// Hosted MySQL, the MySQL job's combined install-and-verify step and
// both EventTransport gate jobs each stage the sibling modules before
// their own verify; the Hosted failover job stages them before packaging
// the jar its arms run.
const mergedStep =
'Install and verify Managed Agent dependencies, tests, Checkstyle, and MySQL integration';
const staging = [
...byName('Install Managed Agent dependencies'),
...byName(mergedStep),
];
expect(staging.length).toBe(5);
for (const step of staging) {
expect(step.run).toContain('packages/sdk-java/qwencode/pom.xml');
expect(step.run).not.toContain('runtime-broker/pom.xml');
}
const enforcing = [
...byName(mergedStep),
...byName('Verify Hosted Java, Spring and MySQL processes'),
...byName('Verify the EventTransport broker gates'),
...byName('Verify the EventTransport multi-node gates'),
];
expect(enforcing.length).toBe(4);
for (const step of enforcing) {
// Match the command line only: the MariaDB job's merged step explains
// its own incident in comments that say 'verify', so a bare
// toContain('verify') would let prose witness the phase after a real
// narrowing to `clean test`.
const commands = step.run
.split('\n')
.filter((line) => !line.trimStart().startsWith('#'))
.join('\n');
expect(commands).toMatch(/\bclean verify\b/);
expect(step.run).not.toContain('spotbugs.skip');
}
// Global invariant: the skip lives nowhere else — not on a new or
// renamed step, and not smuggled in through MAVEN_ARGS at any env level
// (the mvn script reads that variable, so it would not show up in run).
const envBlobs = [
workflow.env,
...Object.values(workflow.jobs).map((job) => job.env),
...steps.map((step) => step.env),
];
for (const env of envBlobs) {
expect(JSON.stringify(env ?? {})).not.toContain('spotbugs.skip');
}
const withSkip = steps.filter((step) =>
step.run?.includes('spotbugs.skip'),
);
expect(withSkip).toEqual([]);
});
});