Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 79 additions & 47 deletions e2e-tests/community-plugin-build-package.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,18 @@ const exec = promisify(require('child_process').exec);

const CONTAINER_TOOL = process.env.CONTAINER_TOOL || 'podman';

const LOG_PREFIX = '[e2e]';

function log(msg: string): void {
console.log(`${LOG_PREFIX} ${msg}`);
}

function logSection(title: string): void {
console.log(`${LOG_PREFIX} --- ${title} ---`);
}

async function downloadFile(url: string, file: string): Promise<void> {
console.log(`Downloading file from ${url} to ${file}`);
log(`Downloading ${url} -> ${file}`);
const response = await axios({
method: 'GET',
url: url,
Expand All @@ -31,23 +41,41 @@ async function runCommand(
command: string,
options: { cwd?: string } = {},
): Promise<{ stdout: string; stderr: string }> {
console.log(
`Executing command: ${command}, in directory: ${options.cwd || process.cwd()}`,
);
const cwd = options.cwd || process.cwd();

const { err, stdout, stderr } = await exec(command, {
shell: true,
...options,
});
console.log(`Command output: ${stdout}`);
console.log(`Command error output: ${stderr}`);
if (err) {
console.error(`Error executing command: ${command}`);
console.error(stderr);
console.error(stdout);
throw err;
try {
const { stdout, stderr } = await exec(command, {
shell: true,
maxBuffer: 10 * 1024 * 1024, // 10MB buffer for large outputs
...options,
});
return { stdout, stderr };
} catch (err: unknown) {
const e = err as {
code?: string | number;
signal?: string;
stdout?: string;
stderr?: string;
};
const out = (e.stdout ?? '').trim() || '(empty)';
const errOut = (e.stderr ?? '').trim() || '(empty)';
const enrichedMessage = [
`Command failed: ${command}`,
`Cwd: ${cwd}`,
`Exit code: ${e.code ?? 'N/A'} | Signal: ${e.signal ?? 'N/A'}`,
'--- stdout ---',
out,
'--- stderr ---',
errOut,
].join('\n');

console.error(`${LOG_PREFIX} COMMAND FAILED: ${command}`);
console.error(`${LOG_PREFIX} cwd: ${cwd}`);
console.error(`${LOG_PREFIX} --- stdout ---\n${out}`);
console.error(`${LOG_PREFIX} --- stderr ---\n${errOut}`);

throw new Error(enrichedMessage);
}
return { stdout, stderr };
}

async function parseDynamicPluginAnnotation(
Expand Down Expand Up @@ -88,9 +116,10 @@ describe('export and package backstage-community plugin', () => {
jest.setTimeout(TEST_TIMEOUT);

beforeAll(async () => {
console.log(`Using rhdh-cli at: ${RHDH_CLI}`);
console.log(`Test workspace: ${tmpDir}`);
console.log(`Container tool: ${CONTAINER_TOOL}`);
logSection('Setup');
log(`rhdh-cli: ${RHDH_CLI}`);
log(`workspace: ${tmpDir}`);
log(`container tool: ${CONTAINER_TOOL}`);

let communityPluginsArchivePath = path.join(
tmpDir,
Expand All @@ -99,26 +128,20 @@ describe('export and package backstage-community plugin', () => {

if (process.env.COMMUNITY_PLUGINS_REPO_ARCHIVE) {
communityPluginsArchivePath = process.env.COMMUNITY_PLUGINS_REPO_ARCHIVE;
console.log(
`Using community plugins repo archive: ${communityPluginsArchivePath}`,
);
log(`Community plugins: path from env: ${communityPluginsArchivePath}`);
}

if (!fs.existsSync(communityPluginsArchivePath)) {
console.log(`Downloading community plugins archive from: ${REPO_URL}`);
await downloadFile(REPO_URL, communityPluginsArchivePath);
console.log(
`Downloaded community plugins archive to: ${communityPluginsArchivePath}`,
if (fs.existsSync(communityPluginsArchivePath)) {
log(
`Community plugins: using existing archive (skipping download): ${communityPluginsArchivePath}`,
);
} else {
console.log(
`Using existing community plugins archive: ${communityPluginsArchivePath}`,
);
log(`Community plugins: archive not found, downloading from ${REPO_URL}`);
await downloadFile(REPO_URL, communityPluginsArchivePath);
log(`Community plugins: downloaded to ${communityPluginsArchivePath}`);
}

console.log(
`Extracting community plugins archive to: ${getClonedRepoPath()}`,
);
log(`Community plugins: extracting to ${getClonedRepoPath()}`);
fs.mkdirSync(getClonedRepoPath(), { recursive: true });
await tar.x({
file: communityPluginsArchivePath,
Expand All @@ -136,29 +159,40 @@ describe('export and package backstage-community plugin', () => {

describe.each([
[
'workspaces/tech-radar/plugins/tech-radar',
'workspaces/tech-radar',
'plugins/tech-radar',
`rhdh-test-tech-radar-frontend:${Date.now()}`,
],
[
'workspaces/tech-radar/plugins/tech-radar-backend',
'workspaces/tech-radar',
'plugins/tech-radar-backend',
`rhdh-test-tech-radar-backend:${Date.now()}`,
],
])('plugin in %s directory', (pluginPath, imageTag) => {
const getFullPluginPath = () => path.join(getClonedRepoPath(), pluginPath);
])('plugin in %s/%s directory', (workspacePath, pluginRelPath, imageTag) => {
const getWorkspacePath = () =>
path.join(getClonedRepoPath(), workspacePath);
const getFullPluginPath = () =>
path.join(getClonedRepoPath(), workspacePath, pluginRelPath);

beforeAll(async () => {
console.log(`Installing dependencies in ${getFullPluginPath()}`);
await runCommand(`yarn install`, {
cwd: getFullPluginPath(),
logSection(`Plugin: ${workspacePath}/${pluginRelPath}`);
log(`Installing dependencies in workspace ${getWorkspacePath()}`);
// Use YARN_ENABLE_SCRIPTS=false to skip native module builds that may fail
await runCommand(`YARN_ENABLE_SCRIPTS=false yarn install`, {
cwd: getWorkspacePath(),
});
console.log(`Compiling TypeScript in ${getFullPluginPath()}`);
await runCommand(`npx tsc`, {
log(`Generating TypeScript declarations in ${getWorkspacePath()}`);
await runCommand(`yarn tsc`, {
cwd: getWorkspacePath(),
});
log(`Building plugin in ${getFullPluginPath()}`);
await runCommand(`yarn build`, {
cwd: getFullPluginPath(),
});
});

afterAll(async () => {
console.log(`Cleaning up image: ${imageTag}`);
log(`Cleaning up image: ${imageTag}`);
await runCommand(`${CONTAINER_TOOL} rmi -f ${imageTag}`);
});

Expand Down Expand Up @@ -198,10 +232,8 @@ describe('export and package backstage-community plugin', () => {
);

const imageMetadata = await getImageMetadata(imageTag);
console.log(
`Image annotations: ${JSON.stringify(imageMetadata.annotations)}`,
);
console.log(`Image labels: ${JSON.stringify(imageMetadata.labels)}`);
log(`Image annotations: ${JSON.stringify(imageMetadata.annotations)}`);
log(`Image labels: ${JSON.stringify(imageMetadata.labels)}`);

// There needs to be at least one annotation (the default dynamic plugin annotation)
expect(imageMetadata.annotations).not.toBeNull();
Expand Down
1 change: 1 addition & 0 deletions e2e-tests/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const tsJestTransformCfg = createDefaultPreset().transform;
/** @type {import("jest").Config} **/
module.exports = {
testEnvironment: 'node',
setupFilesAfterEnv: ['<rootDir>/setup.js'],
transform: {
...tsJestTransformCfg,
},
Expand Down
7 changes: 7 additions & 0 deletions e2e-tests/setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Plain console output for e2e tests (no Jest "console.log" label or stack trace)
console.log = (...args) => {
process.stdout.write(args.map(String).join(' ') + '\n');
};
console.error = (...args) => {
process.stderr.write(args.map(String).join(' ') + '\n');
};
Loading