From b38b30da65938b653cd8ae38028870027d937125 Mon Sep 17 00:00:00 2001 From: Nico Jansen Date: Thu, 21 Nov 2019 18:18:17 +0100 Subject: [PATCH] feat(dashboard-reporter): add github actions ci provider (#1869) Add Github actions to the list of supported CI providers for the dashboard reporter. This makes automagic discovery of `dashboard.project` and `dashboard.version` possible for github actions. --- .../src/reporters/ci/GithubActionsProvider.ts | 22 ++++++++ packages/core/src/reporters/ci/Provider.ts | 5 +- packages/core/src/utils/objectUtils.ts | 4 +- .../ci/GithubActionsProvider.spec.ts | 47 ++++++++++++++++ .../test/unit/reporters/ci/Provider.spec.ts | 54 +++++++++---------- 5 files changed, 103 insertions(+), 29 deletions(-) create mode 100644 packages/core/src/reporters/ci/GithubActionsProvider.ts create mode 100644 packages/core/test/unit/reporters/ci/GithubActionsProvider.spec.ts diff --git a/packages/core/src/reporters/ci/GithubActionsProvider.ts b/packages/core/src/reporters/ci/GithubActionsProvider.ts new file mode 100644 index 0000000000..a73c0c1d18 --- /dev/null +++ b/packages/core/src/reporters/ci/GithubActionsProvider.ts @@ -0,0 +1,22 @@ +import { getEnvironmentVariableOrThrow } from '../../utils/objectUtils'; + +import { CIProvider } from './Provider'; + +/** + * https://help.github.com/en/actions/automating-your-workflow-with-github-actions/using-environment-variables#default-environment-variables + */ +export default class GithubActionsCIProvider implements CIProvider { + public determineProject(): string { + return `github.com/${getEnvironmentVariableOrThrow('GITHUB_REPOSITORY')}`; + } + public determineVersion(): string { + const rawRef = getEnvironmentVariableOrThrow('GITHUB_REF'); + // rawRef will be in the form "refs/pull/:prNumber/merge" or "refs/heads/feat/branch-1" + const [, type, ...name] = rawRef.split('/'); + if (type === 'pull') { + return `PR-${name[0]}`; + } else { + return name.join('/'); + } + } +} diff --git a/packages/core/src/reporters/ci/Provider.ts b/packages/core/src/reporters/ci/Provider.ts index 53e55340ca..2aec53c752 100644 --- a/packages/core/src/reporters/ci/Provider.ts +++ b/packages/core/src/reporters/ci/Provider.ts @@ -2,6 +2,7 @@ import { getEnvironmentVariable } from '../../utils/objectUtils'; import CircleProvider from './CircleProvider'; import TravisProvider from './TravisProvider'; +import GithubActionsCIProvider from './GithubActionsProvider'; /** * Represents an object that can provide information about a CI/CD provider. @@ -28,8 +29,10 @@ export function determineCIProvider() { return new TravisProvider(); } else if (getEnvironmentVariable('CIRCLECI')) { return new CircleProvider(); + } else if (getEnvironmentVariable('GITHUB_ACTION')) { + return new GithubActionsCIProvider(); } - // TODO: Add vsts, github actions and gitlab CI + // TODO: Add vsts and gitlab CI return null; } diff --git a/packages/core/src/utils/objectUtils.ts b/packages/core/src/utils/objectUtils.ts index badca851ca..30cd47fcd2 100644 --- a/packages/core/src/utils/objectUtils.ts +++ b/packages/core/src/utils/objectUtils.ts @@ -1,4 +1,6 @@ import treeKill = require('tree-kill'); +import { StrykerError } from '@stryker-mutator/util'; + export { serialize, deserialize } from 'surrial'; export function freezeRecursively(target: T): T { @@ -33,7 +35,7 @@ export function getEnvironmentVariable(nameEnvironmentVariable: string): string export function getEnvironmentVariableOrThrow(name: string): string { const value = getEnvironmentVariable(name); if (value === undefined) { - throw new Error(`Missing environment variable "${name}"`); + throw new StrykerError(`Missing environment variable "${name}"`); } else { return value; } diff --git a/packages/core/test/unit/reporters/ci/GithubActionsProvider.spec.ts b/packages/core/test/unit/reporters/ci/GithubActionsProvider.spec.ts new file mode 100644 index 0000000000..5179834555 --- /dev/null +++ b/packages/core/test/unit/reporters/ci/GithubActionsProvider.spec.ts @@ -0,0 +1,47 @@ +import { expect } from 'chai'; +import { StrykerError } from '@stryker-mutator/util'; + +import GithubActionsCIProvider from '../../../../src/reporters/ci/GithubActionsProvider'; +import { EnvironmentVariableStore } from '../../../helpers/EnvironmentVariableStore'; + +describe(GithubActionsCIProvider.name, () => { + const env = new EnvironmentVariableStore(); + let sut: GithubActionsCIProvider; + beforeEach(() => { + sut = new GithubActionsCIProvider(); + }); + afterEach(() => { + env.restore(); + }); + + describe('determineProject()', () => { + it('should return the appropriate value', () => { + env.set('GITHUB_REPOSITORY', 'stryker/stryker'); + const result = sut.determineProject(); + expect(result).eq('github.com/stryker/stryker'); + }); + + it('should throw if env variable is missing', () => { + env.unset('GITHUB_REPOSITORY'); + expect(() => sut.determineProject()).throws(StrykerError); + }); + }); + + describe('determineVersion()', () => { + it('should retrieve the PR name', () => { + //"refs/pull/:prNumber/merge" or "refs/heads/feat/branch-1" + env.set('GITHUB_REF', 'refs/pull/156/merge'); + expect(sut.determineVersion()).eq('PR-156'); + }); + + it('should retrieve the branch name', () => { + env.set('GITHUB_REF', 'refs/heads/feat/branch-1'); + expect(sut.determineVersion()).eq('feat/branch-1'); + }); + + it('should throw if env variable is missing', () => { + env.unset('GITHUB_REF'); + expect(() => sut.determineVersion()).throws(StrykerError); + }); + }); +}); diff --git a/packages/core/test/unit/reporters/ci/Provider.spec.ts b/packages/core/test/unit/reporters/ci/Provider.spec.ts index 01f8b88093..7430204d61 100644 --- a/packages/core/test/unit/reporters/ci/Provider.spec.ts +++ b/packages/core/test/unit/reporters/ci/Provider.spec.ts @@ -1,43 +1,43 @@ import { expect } from 'chai'; -import * as sinon from 'sinon'; -import * as environmentVariables from '../../../../src/utils/objectUtils'; import { determineCIProvider } from '../../../../src/reporters/ci/Provider'; +import TravisProvider from '../../../../src/reporters/ci/TravisProvider'; +import CircleProvider from '../../../../src/reporters/ci/CircleProvider'; +import { EnvironmentVariableStore } from '../../../helpers/EnvironmentVariableStore'; +import GithubActionsProvider from '../../../../src/reporters/ci/GithubActionsProvider'; describe('determineCiProvider()', () => { - let getEnvironmentVariables: sinon.SinonStub; + const env = new EnvironmentVariableStore(); beforeEach(() => { - getEnvironmentVariables = sinon.stub(environmentVariables, 'getEnvironmentVariable'); + env.unset('HAS_JOSH_K_SEAL_OF_APPROVAL'); + env.unset('CIRCLECI'); + env.unset('GITHUB_ACTION'); }); - - describe('Without CI environment', () => { - it('should not select a CI Provider', () => { - getEnvironmentVariables.withArgs('HAS_JOSH_K_SEAL_OF_APPROVAL').returns(''); - - const result = determineCIProvider(); - - expect(result).to.be.null; - }); + afterEach(() => { + env.restore(); }); - describe("When HAS_JOSH_K_SEAL_OF_APPROVAL is 'true'", () => { - it('should provide a CI Provider implementation', () => { - getEnvironmentVariables.withArgs('HAS_JOSH_K_SEAL_OF_APPROVAL').returns(true); - - const result = determineCIProvider(); - - expect(result).to.be.not.undefined; - }); + it('should not select a CI Provider when not in a CI environment', () => { + const result = determineCIProvider(); + expect(result).to.be.null; }); - describe("When CIRCLECI is 'true'", () => { - it('should provide a CI Provider implementation', () => { - getEnvironmentVariables.withArgs('CIRCLECI').returns(true); + it('should provide Travis when running in the travis environment', () => { + env.set('HAS_JOSH_K_SEAL_OF_APPROVAL', 'true'); + const result = determineCIProvider(); + expect(result).instanceOf(TravisProvider); + }); - const result = determineCIProvider(); + it('should provide CircleCI when running in the circle CI environment', () => { + env.set('CIRCLECI', 'true'); + const result = determineCIProvider(); + expect(result).instanceOf(CircleProvider); + }); - expect(result).to.be.not.undefined; - }); + it('should provide Github when running in the github actions CI environment', () => { + env.set('GITHUB_ACTION', 'true'); + const result = determineCIProvider(); + expect(result).instanceOf(GithubActionsProvider); }); });