Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for viewing parallel test runs to CI plugin #980

Merged
merged 31 commits into from
Aug 14, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9fa8f38
Ran oclif plugin and start ci:info
raulb Aug 3, 2018
34391d8
Update dependencies and config
raulb Aug 7, 2018
a25c2d3
Add color
raulb Aug 7, 2018
8e0855d
Update heroku ci interfaces
raulb Aug 7, 2018
14fc94f
Update to use latest API schema
raulb Aug 7, 2018
7d884f0
Remove variant
raulb Aug 7, 2018
dcc7e55
Upgrade heroku-cli/schema
raulb Aug 8, 2018
2d8e976
ci:info working
raulb Aug 8, 2018
94f67a5
Adapt code to ts
raulb Aug 8, 2018
c9149eb
Restructure in utils
raulb Aug 8, 2018
837e3c9
Add ci:last
raulb Aug 8, 2018
df55ab9
Add tests to ci:info
raulb Aug 9, 2018
c422be2
Pulled interfaces from Kolkrabbi schema
raulb Aug 9, 2018
f11c781
WIP migrating ci:run
raulb Aug 9, 2018
2f6eb76
Move utilities and update dependencies
raulb Aug 10, 2018
c45bf8d
Use the right host
raulb Aug 10, 2018
50c110c
Add logs back
raulb Aug 10, 2018
0ac2849
using cli-ux to show progress and completion
jabrown85 Aug 10, 2018
a68e114
Display of test run works for single and multi-node
jabrown85 Aug 10, 2018
f0f5a8f
Add rerun command
jabrown85 Aug 10, 2018
6147f3e
Removing commands in favor of new commands
jabrown85 Aug 10, 2018
95050da
Fixed up tests
jabrown85 Aug 10, 2018
7b354c8
Delete test no longer necessary
raulb Aug 13, 2018
3eda3c6
Add typed sinon
raulb Aug 13, 2018
13387c0
Add test for run
raulb Aug 13, 2018
d461f42
Added new package to cli, got run command tests working
jabrown85 Aug 13, 2018
5c060b9
Adding re-run tests
jabrown85 Aug 13, 2018
8a88c30
more tests
jabrown85 Aug 13, 2018
da144ef
Updated readme and updated another test
jabrown85 Aug 13, 2018
213bd06
simplify test
jabrown85 Aug 13, 2018
6067b14
removing try...catch that was making test the error codes harder
jabrown85 Aug 13, 2018
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
Prev Previous commit
Next Next commit
Move utilities and update dependencies
  • Loading branch information
raulb committed Aug 14, 2018
commit 2f6eb7617fb914830b7e3014e4b2fbc85f97056c
5 changes: 3 additions & 2 deletions packages/ci/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
"dependencies": {
"@heroku-cli/color": "^1.1.9",
"@heroku-cli/command": "^8.1.26",
"@heroku-cli/schema": "^1.0.11",
"@heroku-cli/schema": "^1.0.12",
"@oclif/command": "^1",
"@oclif/config": "^1",
"@types/bluebird": "^3.5.23",
"async-file": "^2.0.2",
"@types/inquirer": "0.0.42",
"@types/validator": "^9.4.1",
"got": "^8.3.2",
"inquirer": "^6.0.0",
"tslib": "^1",
"validator": "^10.5.0"
Expand Down
4 changes: 2 additions & 2 deletions packages/ci/src/commands/ci/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import * as Heroku from '@heroku-cli/schema'

import {Command, flags} from '@heroku-cli/command'

import {getPipeline} from '../../lib/utils/pipelines'
import {displayTestRunInfo} from '../../lib/utils/test-run'
import {getPipeline} from '../../utils/pipelines'
import {displayTestRunInfo} from '../../utils/test-run'

export default class CiInfo extends Command {
static description = 'show the status of a specific test run'
Expand Down
4 changes: 2 additions & 2 deletions packages/ci/src/commands/ci/last.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import * as Heroku from '@heroku-cli/schema'

import {Command, flags} from '@heroku-cli/command'

import {getPipeline} from '../../lib/utils/pipelines'
import {displayTestRunInfo} from '../../lib/utils/test-run'
import {getPipeline} from '../../utils/pipelines'
import {displayTestRunInfo} from '../../utils/test-run'

export default class CiLast extends Command {
static description = 'looks for the most recent run and returns the output of that run'
Expand Down
29 changes: 17 additions & 12 deletions packages/ci/src/commands/ci/run.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import {Command, flags} from '@heroku-cli/command'

import {createSourceBlob} from '../../lib/utils/source'
import {displayTestRunInfo} from '../../lib/utils/test-run'
import {Command, flags} from '@heroku-cli/command'
import * as Heroku from '@heroku-cli/schema'

import {getPipeline} from '../../lib/utils/pipelines'
import * as Kolkrabbi from '../../interfaces/kolkrabbi'

import {readCommit} from '../../lib/utils/git'
import {getPipeline} from '../../utils/pipelines'
import {displayAndExit} from '../../utils/test-run'

import * as Kolkrabbi from '../../interfaces/kolkrabbi'
import {createSourceBlob} from '../../utils/source'

const git = require('../../utils/git')
export default class CiRun extends Command {
static description = 'run tests against current directory'

Expand All @@ -25,25 +26,29 @@ export default class CiRun extends Command {
async run() {
const {flags} = this.parse(CiRun)
const pipeline = await getPipeline(flags, this)
const commit = await readCommit('HEAD')
const commit = await git.readCommit('HEAD')

this.log('Preparing source')
const sourceBlobUrl = await createSourceBlob(commit.ref, this.heroku)
const sourceBlobUrl = await createSourceBlob(commit.ref, this)
const {body: pipelineRepository} = await this.heroku.get<Kolkrabbi.KolkrabbiApiPipelineRepositories>(`/pipelines/${pipeline.id}/repository`, {hostname: 'https://kolkrabbi.heroku.com'})
const organization = pipelineRepository.organization && pipelineRepository.organization.name

this.log('Starting test run')

const {body: testRun} = this.heroku.post('/test-runs', {body: {
try {
const {body: testRun} = await this.heroku.post<Heroku.TestRun>('/test-runs', {body: {
commit_branch: commit.branch,
commit_message: commit.message,
commit_sha: commit.ref,
pipeline: pipeline.id,
organization,
source_blob_url: sourceBlobUrl
}
})
}
})

// return yield TestRun.displayAndExit(pipeline, testRun.number, { heroku })
await displayAndExit(pipeline, testRun.number!, this)
} catch (e) {
this.error(e) // This currently shows a › Error: Not found.
}
}
}
56 changes: 0 additions & 56 deletions packages/ci/src/lib/utils/source.ts

This file was deleted.

41 changes: 22 additions & 19 deletions packages/ci/src/lib/utils/git.ts → packages/ci/src/utils/git.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
const spawn = require('child_process').spawn
import {Promise} from 'bluebird'

const Promise = require('bluebird')
const fs = Promise.promisifyAll(require('fs'))
const tmp = Promise.promisifyAll(require('temp').track())

const gh = require('github-url-to-object')

const NOT_A_GIT_REPOSITORY = 'not a git repository'
const RUN_IN_A_GIT_REPOSITORY = 'Please run this command from the directory containing your project\'s git repo'

const NOT_ON_A_BRANCH = 'not a symbolic ref'
const CHECKOUT_A_BRANCH = 'Please checkout a branch before running this command'

function runGit(...args: any[]) {
function runGit (...args) {
const git = spawn('git', args)

return new Promise((resolve: any, reject: any) => {
git.on('exit', (exitCode: number) => {
return new Promise((resolve, reject) => {
git.on('exit', (exitCode) => {
if (exitCode === 0) {
return
}
Expand All @@ -32,35 +31,35 @@ function runGit(...args: any[]) {
reject(`Error while running 'git ${args.join(' ')}' (${error})`)
})

git.stdout.on('data', (data: any) => resolve(data.toString().trim()))
git.stdout.on('data', (data) => resolve(data.toString().trim()))
})
}

export async function getRef(branch: any) {
function * getRef (branch) {
return runGit('rev-parse', branch || 'HEAD')
}

export async function getBranch(symbolicRef: any) {
function * getBranch (symbolicRef) {
return runGit('symbolic-ref', '--short', symbolicRef)
}

export async function getCommitTitle(ref: any) {
function * getCommitTitle (ref) {
return runGit('log', ref || '', '-1', '--pretty=format:%s')
}

export async function createArchive(ref: any) {
function * createArchive (ref) {
const tar = spawn('git', ['archive', '--format', 'tar.gz', ref])
const file = await tmp.openAsync({suffix: '.tar.gz'})
const file = yield tmp.openAsync({ suffix: '.tar.gz' })
const write = tar.stdout.pipe(fs.createWriteStream(file.path))

return new Promise((resolve: any, reject: any) => {
return new Promise((resolve, reject) => {
write.on('close', () => resolve(file.path))
write.on('error', reject)
})
}

export async function githubRepository() {
const remote = await runGit('remote', 'get-url', 'origin')
function * githubRepository () {
const remote = yield runGit('remote', 'get-url', 'origin')
const repository = gh(remote)

if (repository === null) {
Expand All @@ -70,16 +69,20 @@ export async function githubRepository() {
return repository
}

function * readCommit(commit: any) {
function * readCommit (commit) {
const branch = yield getBranch('HEAD')
const ref = yield getRef(commit)
const message = yield getCommitTitle(ref)

return Promise.resolve({branch, ref, message})
return Promise.resolve({
branch: branch,
ref: ref,
message: message
})
}

module.exports = {
createArchive,
readCommit,
githubRepository,
readCommit
createArchive
}
49 changes: 49 additions & 0 deletions packages/ci/src/utils/source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict'
const got = require('got')
const git = require('./git')
import {Command} from '@heroku-cli/command'

import * as fs from 'async-file'

async function uploadArchive(url: string, filePath: string) {
const request = got.stream.put(url, {
headers: {
'content-length': (await fs.stat(filePath)).size
}
})

fs.createReadStream(filePath).pipe(request)

return new Promise((resolve: any, reject: any) => {
request.on('error', reject)
request.on('response', resolve)
})
}

async function prepareSource(ref: any, command: Command) {
const [filePath, source] = await [
git.createArchive(ref),
command.heroku.post('/sources', {body: command})
]
await uploadArchive(source.source_blob.put_url, filePath)
return Promise.resolve(source)
}

async function urlExists(url: any) {
return got.head(url)
}

export async function createSourceBlob(ref: any, command: Command) {
try {
const githubRepository = await git.githubRepository()
const {user, repo} = githubRepository

let {body: archiveLink} = await command.heroku.get<any>(`/github/repos/${user}/${repo}/tarball/${ref}`, {hostname: 'https://kolkrabbi.heroku.com'})
if (await urlExists(archiveLink.archive_link)) {
return archiveLink.archive_link
}
} catch (ex) { command.error(ex) }

const sourceBlob = await prepareSource(ref, command)
return sourceBlob.source_blob.get_url
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,60 @@ async function renderNodeOutput(command: Command, testRun: Heroku.TestRun, testN
command.log(printLine(testRun))
}

const BUILDING = 'building'
const RUNNING = 'running'
const ERRORED = 'errored'
const FAILED = 'failed'
const SUCCEEDED = 'succeeded'
const CANCELLED = 'cancelled'

const TERMINAL_STATES = [SUCCEEDED, FAILED, ERRORED, CANCELLED]
const RUNNING_STATES = [RUNNING].concat(TERMINAL_STATES)
const BUILDING_STATES = [BUILDING, RUNNING].concat(TERMINAL_STATES)

async function waitForStates(states: any, testRun: Heroku.TestRun, command: Command) {
let newTestRun

while (!states.includes(testRun.status)) {
let {body: bodyTestRun} = await command.heroku.get<Heroku.TestRun>(`/pipelines/${testRun.pipeline!.id}/test-runs/${testRun.number}`)
newTestRun = bodyTestRun
}
return newTestRun
}

async function display(pipeline: Heroku.Pipeline, number: number, command: Command) {
let {body: testRun} = await command.heroku.get<Heroku.TestRun | undefined>(`/pipelines/${pipeline.id}/test-runs/${number}`)
if (testRun) {
let {body: testNodes} = await command.heroku.get<Heroku.TestNode[]>(`/test-runs/${testRun.id}/test-nodes`)
let firstTestNode = testNodes[0]

if (testRun) { testRun = await waitForStates(BUILDING_STATES, testRun, command) }
if (firstTestNode) { await stream(firstTestNode.setup_stream_url!) }

if (testRun) { testRun = await waitForStates(RUNNING_STATES, testRun, command) }
if (firstTestNode) { await stream(firstTestNode.output_stream_url!) }

if (testRun) { testRun = await waitForStates(TERMINAL_STATES, testRun, command) }

// At this point, we know that testRun has a finished status,
// and we can check for exit_code from firstTestNode
if (testRun) {
let {body: newTestNodes} = await command.heroku.get<Heroku.TestNode[]>(`/test-runs/${testRun.id}/test-nodes`)
firstTestNode = newTestNodes[0]

command.log()
command.log(printLine(testRun))
}
return firstTestNode
}
}

export async function displayAndExit(pipeline: any, number: number, command: Command) {
let testNode = await display(pipeline, number, command)

testNode && testNode.exit_code ? process.exit(testNode.exit_code) : process.exit(1)
}

export async function displayTestRunInfo(command: Command, testRun: Heroku.TestRun, testNodes: Heroku.TestNode[], nodeArg: string | undefined) {
let testNode: Heroku.TestNode

Expand Down
Loading