diff --git a/.circleci/config.yml b/.circleci/config.yml index da54155fc..6735ebdaa 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -159,7 +159,8 @@ jobs: command: npm run system-test environment: GCLOUD_PROJECT: long-door-651 - GOOGLE_APPLICATION_CREDENTIALS: .circleci/key.json + GOOGLE_APPLICATION_CREDENTIALS: /home/node/project/.circleci/key.json + NPM_CONFIG_PREFIX: /home/node/.npm-global - run: name: Remove unencrypted key. command: | diff --git a/.circleci/key.json.enc b/.circleci/key.json.enc new file mode 100644 index 000000000..1dbf9a0b6 Binary files /dev/null and b/.circleci/key.json.enc differ diff --git a/package.json b/package.json index 22c612b8e..92ba81aec 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@types/node": "^10.3.2", "@types/proxyquire": "^1.3.28", "@types/pumpify": "^1.4.1", + "@types/rimraf": "^2.0.2", "@types/semver": "^5.5.0", "@types/sinon": "^5.0.1", "@types/source-map-support": "^0.4.1", @@ -45,9 +46,11 @@ "istanbul": "~0.4.5", "jsdoc": "^3.5.5", "mocha": "~5.2.0", + "nyc": "^13.1.0", "pegjs": "~0.10.0", "proxyquire": "^2.0.1", "pumpify": "^1.5.1", + "rimraf": "^2.6.2", "sinon": "^7.0.0", "source-map-support": "^0.5.6", "stream-events": "^1.0.4", @@ -67,7 +70,7 @@ "prepare": "npm run compile", "pretest-only": "npm run compile", "posttest": "npm run lint", - "system-test": "echo no system tests 😱", + "system-test": "nyc mocha --timeout 1200000 build/system-test/system.js", "samples-test": "echo no sample tests 😱" }, "repository": "googleapis/gax-nodejs", @@ -82,5 +85,11 @@ "homepage": "https://github.com/googleapis/gax-nodejs#readme", "engines": { "node": ">=6.0.0" + }, + "nyc": { + "exclude": [ + "build/system-test/**", + "system-test-run/**" + ] } } diff --git a/system-test-run/nodejs-speech b/system-test-run/nodejs-speech new file mode 160000 index 000000000..4ba57015f --- /dev/null +++ b/system-test-run/nodejs-speech @@ -0,0 +1 @@ +Subproject commit 4ba57015f10fb9bbf0e079f196d9558f13e684f7 diff --git a/system-test/system.ts b/system-test/system.ts index 4672462af..3918b3f02 100644 --- a/system-test/system.ts +++ b/system-test/system.ts @@ -14,4 +14,135 @@ * limitations under the License. */ -console.warn(`no system tests available 👻`); +import * as cp from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; +import * as util from 'util'; + +const mkdir = util.promisify(fs.mkdir); +const readFile = util.promisify(fs.readFile); +const writeFile = util.promisify(fs.writeFile); +const rmrf = util.promisify(rimraf); + +const baseRepoUrl = 'https://github.com/googleapis/'; +const gaxPath = process.cwd(); +const testDir = path.join(process.cwd(), 'system-test-run'); + +interface PackageJson { + dependencies: {[name: string]: string;}; +} + +interface ExecuteResult { + stdout: string; + stderr: string; +} + +async function execute(command: string, cwd?: string): Promise { + cwd = cwd || process.cwd(); + const maxBuffer = 10 * 1024 * 1024; + console.log(`Execute: ${command} [cwd: ${cwd}]`); + return new Promise((resolve, reject) => { + cp.exec(command, {cwd, maxBuffer}, (err, stdout, stderr) => { + if (err) { + reject(new Error(`Command ${command} terminated with error ${err}`)); + } else { + resolve({stdout, stderr}); + } + }); + }); +} + +async function spawn( + command: string, args?: string[], cwd?: string): Promise { + cwd = cwd || process.cwd(); + args = args || []; + console.log(`Execute: ${command} ${args.join(' ')} [cwd: ${cwd}]`); + return new Promise((resolve, reject) => { + const child = cp.spawn(command, args || [], { + cwd, + stdio: 'inherit' + }).on('close', (code: number|null, signal: string|null) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`Command ${command} terminated with code ${ + code}, signal ${signal}`)); + } + }); + }); +} + +async function latestRelease(cwd: string): Promise { + const gitTagOutput = (await execute('git tag --list', cwd)).stdout; + const tags = + gitTagOutput.split('\n') + .filter(str => str.match(/^v\d+\.\d+\.\d+$/)) + .sort((tag1: string, tag2: string): number => { + const match1 = tag1.match(/^v(\d+)\.(\d+)\.(\d+)$/); + const match2 = tag2.match(/^v(\d+)\.(\d+)\.(\d+)$/); + if (!match1 || !match2) { + throw new Error(`Cannot compare git tags ${tag1} and ${tag2}`); + } + // compare major version, then minor versions, then patch versions. + // return positive number, zero, or negative number + for (let idx = 1; idx <= 3; ++idx) { + if (match1[idx] !== match2[idx]) { + return Number(match1[idx]) - Number(match2[idx]); + } + } + return 0; + }); + // the last tag in the list is the latest release + return tags[tags.length - 1]; +} + +async function preparePackage(packageName: string): Promise { + await spawn( + 'git', ['clone', `${baseRepoUrl}${packageName}.git`, packageName]); + const tag = await latestRelease(packageName); + await spawn('git', ['checkout', tag], packageName); + await spawn('npm', ['link', '../../'], packageName); + // npm-install-retry prints a lot, so run it silently + await execute('node .circleci/npm-install-retry.js', packageName); +} + +async function runSystemTest(packageName: string): Promise { + await spawn('npm', ['run', 'system-test'], packageName); +} + +describe('Run system tests for some libraries', () => { + before(async () => { + await rmrf(testDir); + await mkdir(testDir); + process.chdir(testDir); + console.log(`Running tests in ${testDir}.`); + }); + // Video intelligence API has long running operations + describe('video-intelligence', () => { + before(async () => { + await preparePackage('nodejs-video-intelligence'); + }); + it('should pass system tests', async () => { + await runSystemTest('nodejs-video-intelligence'); + }); + }); + // Pub/Sub has streaming methods and pagination + describe('pubsub', () => { + before(async () => { + await preparePackage('nodejs-pubsub'); + }); + it('should pass system tests', async () => { + await runSystemTest('nodejs-pubsub'); + }); + }); + // Speech only has smoke tests, but still... + describe('speech', () => { + before(async () => { + await preparePackage('nodejs-speech'); + }); + it('should pass system tests', async () => { + await runSystemTest('nodejs-speech'); + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 0e62ff7b4..07c70b201 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ }, "include": [ "src/*.ts", - "test/*.ts" + "test/*.ts", + "system-test/*.ts" ] }