-
Notifications
You must be signed in to change notification settings - Fork 246
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: CDK Build Integration Test (#1219)
* feat: CDK Build Integration Test Adds @jsii/integ-test private module for defining new integration tests. Adds a new integration test that downloads the latest CDK release source code and builds it with the local version of jsii and jsii-pacmak. This unit test requires a github access token defined in the environment to get the latest release version and download the asset. Fixes: #1209
- Loading branch information
1 parent
aa28ef6
commit e99d722
Showing
10 changed files
with
325 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
GITHUB_TOKEN=personal_access_token |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
--- | ||
extends: ../../../eslint-config.yaml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.env | ||
*.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# JSII Integration Tests | ||
|
||
A suite of integration tests for JSII and related modules. | ||
|
||
## Running | ||
|
||
Running the integration tests locally requires a github access token. Copy the | ||
`.env.example` file and replace the dummy value with a personal access token. | ||
|
||
then run `yarn run test:integ` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
{ | ||
"name": "@jsii/integ-test", | ||
"version": "0.22.0", | ||
"description": "A suite of integration tests for JSII and related modules.", | ||
"private": true, | ||
"scripts": { | ||
"build": "tsc --build && npm run lint", | ||
"lint": "eslint . --ext .ts --ignore-path=.gitignore", | ||
"test:integ": "jest" | ||
}, | ||
"keywords": [], | ||
"author": { | ||
"name": "Amazon Web Services", | ||
"url": "https://aws.amazon.com" | ||
}, | ||
"license": "Apache-2.0", | ||
"dependencies": { | ||
"tar": "^6.0.1" | ||
}, | ||
"devDependencies": { | ||
"@octokit/rest": "^16.36.0", | ||
"@types/dotenv": "^8.2.0", | ||
"@types/fs-extra": "^8.0.1", | ||
"@types/tar": "^4.0.3", | ||
"dotenv": "^8.2.0", | ||
"eslint": "^6.8.0", | ||
"fs-extra": "^8.1.0", | ||
"jest": "^25.1.0", | ||
"jsii": "^0.22.0", | ||
"jsii-pacmak": "^0.22.0", | ||
"jsii-rosetta": "^0.22.0", | ||
"typescript": "~3.7.5" | ||
}, | ||
"jest": { | ||
"errorOnDeprecated": true, | ||
"testEnvironment": "node", | ||
"testMatch": [ | ||
"**/?(*.)+(spec|test).js" | ||
] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import * as Octokit from '@octokit/rest'; | ||
import * as dotenv from 'dotenv'; | ||
import { mkdtemp, remove } from 'fs-extra'; | ||
import * as path from 'path'; | ||
import { downloadReleaseAsset, minutes, ProcessManager, extractFileStream } from '../utils'; | ||
|
||
dotenv.config(); | ||
const JSII_DIR = path.dirname(require.resolve('jsii/package.json')); | ||
const JSII_PACMAK_DIR = path.dirname(require.resolve('jsii-pacmak/package.json')); | ||
const JSII_ROSETTA_DIR = path.dirname(require.resolve('jsii-rosetta/package.json')); | ||
|
||
const octokit = new Octokit({ | ||
auth: process.env.GITHUB_TOKEN | ||
}); | ||
|
||
describe('Build CDK', () => { | ||
let buildDir: string; | ||
let processes: ProcessManager; | ||
|
||
beforeAll(async () => { | ||
processes = new ProcessManager(); | ||
buildDir = await mkdtemp(path.join(__dirname, 'build')); | ||
}); | ||
|
||
afterAll(async () => { | ||
await processes.killAll(); | ||
await remove(buildDir); | ||
}); | ||
|
||
test('can build latest cdk release', async () => { | ||
// download latest release info | ||
const release = await octokit.repos.getLatestRelease({ | ||
owner: 'aws', | ||
repo: 'aws-cdk' | ||
}); | ||
|
||
// download and extract code | ||
const code = await downloadReleaseAsset(`https://api.github.com/repos/aws/aws-cdk/tarball/${release.data.tag_name}`); | ||
const srcDir = path.join(buildDir, `aws-aws-cdk-${release.data.target_commitish.substring(0, 7)}`); | ||
await extractFileStream(code, buildDir); | ||
|
||
// install cdk dependencies | ||
await processes.spawn('yarn', ['install', '--frozen-lockfile'], { | ||
cwd: srcDir | ||
}); | ||
|
||
// build cdk build tools | ||
await processes.spawn('npx', [ | ||
'lerna', | ||
'--stream', | ||
'--scope', 'cdk-build-tools', | ||
'--scope', 'pkglint', | ||
'--scope', 'awslint', | ||
'run', 'build', | ||
], { cwd: srcDir }); | ||
|
||
// build jsii modules | ||
await processes.spawn('npx', [ | ||
'lerna', | ||
'--stream', | ||
'--scope', '@aws-cdk/*', | ||
'--scope', 'aws-cdk', | ||
'run', 'build', | ||
'--', '--jsii', path.join(JSII_DIR, 'bin', 'jsii'), | ||
], { cwd: srcDir }); | ||
|
||
// build the rest | ||
await processes.spawn('npx', [ | ||
'lerna', | ||
'--stream', | ||
'--ignore', '@aws-cdk/*', | ||
'--ignore', 'aws-cdk', | ||
'--ignore', 'cdk-build-tools', | ||
'--ignore', 'pkglint', | ||
'--ignore', 'awslint', | ||
'run', 'build', | ||
], { cwd: srcDir }); | ||
|
||
// package modules | ||
await processes.spawn('./pack.sh', [], { | ||
cwd: srcDir, | ||
env: { | ||
PACMAK: path.join(JSII_PACMAK_DIR, 'bin', 'jsii-pacmak'), | ||
ROSETTA: path.join(JSII_ROSETTA_DIR, 'bin', 'jsii-rosetta') | ||
} | ||
}); | ||
}, minutes(60)); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"compilerOptions": { | ||
"composite": false, | ||
"declaration": false | ||
}, | ||
"extends": "../../../tsconfig-base", | ||
"include": ["**/*.ts"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import * as cp from 'child_process'; | ||
import { IncomingMessage } from 'http'; | ||
import * as https from 'https'; | ||
import { Readable } from 'stream'; | ||
import { extract } from 'tar'; | ||
|
||
/** | ||
* @param num a quantity of minutes (could be fractional) | ||
* | ||
* @return equivalent number of milliseconds | ||
*/ | ||
export function minutes(num: number): number { | ||
return num * 1000 * 60; | ||
} | ||
|
||
/** | ||
* Used to track and clean up processes if tests fail or timeout | ||
*/ | ||
export class ProcessManager { | ||
private readonly processes: { | ||
[pid: string]: { | ||
proc: cp.ChildProcess; | ||
promise: Promise<void>; | ||
}; | ||
} = {}; | ||
|
||
/** | ||
* kill all still running processes | ||
* | ||
* @param signal sent to terminate process | ||
*/ | ||
public async killAll(signal?: string) { | ||
const values = Object.values(this.processes); | ||
await Promise.all(values.map(({ proc, promise }) => async() => { | ||
proc.kill(signal); | ||
await promise; | ||
this.remove(proc); | ||
})); | ||
} | ||
|
||
private add(proc: cp.ChildProcess, promise: Promise<void>) { | ||
this.processes[proc.pid] = { proc, promise }; | ||
} | ||
|
||
private remove(proc: cp.ChildProcess) { | ||
delete this.processes[proc.pid]; | ||
} | ||
|
||
/** | ||
* spawn new child process | ||
* | ||
* @param shell command being called | ||
* @param arguments passed to command | ||
* @param options passed to child process spawn | ||
*/ | ||
public async spawn(cmd: string, args: string[], opts: any = {}): Promise<void> { | ||
const proc = cp.spawn(cmd, args, { stdio: 'inherit', ...opts }); | ||
|
||
const promise = new Promise<void>((ok, ko) => { | ||
proc.once('exit', code => { | ||
const message = `child process exited with code: ${code}`; | ||
if (code === 0) { | ||
process.stdout.write(message); | ||
ok(); | ||
} else { | ||
process.stderr.write(message); | ||
ko(new Error(message)); | ||
} | ||
|
||
this.remove(proc); | ||
}); | ||
|
||
proc.once('error', error => { | ||
process.stderr.write(`Process ${proc.pid} error: ${error}`); | ||
ko(); | ||
}); | ||
}); | ||
|
||
this.add(proc, promise); | ||
return promise; | ||
} | ||
} | ||
|
||
/** | ||
* write downloaded asset to file | ||
* | ||
* @param source stream | ||
* @param destination directory for extracted files | ||
*/ | ||
export async function extractFileStream(source: Readable, destination: string) { | ||
return new Promise((ok, ko) => { | ||
const destStream = extract({ cwd: destination }); | ||
destStream.once('close', ok); | ||
destStream.once('error', ko); | ||
source.once('error', ko); | ||
|
||
source.pipe(destStream); | ||
}); | ||
} | ||
|
||
/** | ||
* Wrap http calls to download release asset in a promise. Github responds with | ||
* a 302 sometimes which is required to be handled. Returns the buffer to be | ||
* streamed to destination fs stream. | ||
* | ||
* @param url of downloadable asset | ||
* | ||
* @returns readable stream of asset data | ||
*/ | ||
export async function downloadReleaseAsset(url: string): Promise<Readable> { | ||
return new Promise((ok, ko) => { | ||
const config = { | ||
headers: { | ||
'User-Agent': '@jsii/integ-test', | ||
Authorization: `token ${process.env.GITHUB_TOKEN}` | ||
} | ||
}; | ||
|
||
https.get(url, config, (response: IncomingMessage) => { | ||
if (response.statusCode === 302) { | ||
|
||
if (!response.headers.location) { | ||
throw new Error('Bad redirect, no location header found'); | ||
} | ||
|
||
return https.get(response.headers.location, config, ok); | ||
} else if (response.statusCode && (response.statusCode < 200 || response.statusCode > 300)) { | ||
return ko(new Error(`Status Code: ${response.statusCode}`)); | ||
} | ||
|
||
return ok(response); | ||
}); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters