def test_parse_vault_with_env_error(app): s = '''vault: url: http://localhost:8200 token: abc123 env: API_TOKEN secret/api token ''' f = io.StringIO(s) with pytest.raises(InvalidSpecification): Specification.parse_file(f)
def test_deploy_single_provider_object_should_fail(app): from badwolf.exceptions import InvalidSpecification s = """deploy: script: echo test tag: true""" f = io.StringIO(s) with pytest.raises(InvalidSpecification): Specification.parse_file(f)
def test_parse_image(app): s = """script: ls""" f = io.StringIO(s) spec = Specification.parse_file(f) assert not spec.image s = """image: python""" f = io.StringIO(s) spec = Specification.parse_file(f) assert spec.image == 'python:latest' s = """image: python:2.7""" f = io.StringIO(s) spec = Specification.parse_file(f) assert spec.image == 'python:2.7'
def test_parse_docker(app): s = """docker: True""" f = io.StringIO(s) spec = Specification.parse_file(f) assert spec.docker s = """docker: no""" f = io.StringIO(s) spec = Specification.parse_file(f) assert not spec.docker s = """linter: flake8""" f = io.StringIO(s) spec = Specification.parse_file(f) assert not spec.docker
def test_parse_privileged(app): s = """privileged: True""" f = io.StringIO(s) spec = Specification.parse_file(f) assert spec.privileged s = """privileged: no""" f = io.StringIO(s) spec = Specification.parse_file(f) assert not spec.privileged s = """linter: flake8""" f = io.StringIO(s) spec = Specification.parse_file(f) assert not spec.privileged
def test_jsonlint_a_json_changes_in_range(app, pr_context): diff = """diff --git a/b.json b/b.json index 6ebebfe..6be8d74 100644 --- a/b.json +++ b/b.json @@ -1,3 +1,4 @@ { "a": 1 + "b": 2 } """ spec = Specification() spec.linters.append(ObjectDict(name='jsonlint', pattern=None)) lint = LintProcessor(pr_context, spec, os.path.join(FIXTURES_PATH, 'jsonlint')) patch = PatchSet(diff.split('\n')) with mock.patch.object(lint, 'load_changes') as load_changes,\ mock.patch.object(lint, 'update_build_status') as build_status,\ mock.patch.object(lint, '_report') as report: load_changes.return_value = patch build_status.return_value = None report.return_value = (1, 2) lint.problems.set_changes(patch) lint.process() assert load_changes.called assert len(lint.problems) == 1 problem = lint.problems[0] assert problem.filename == 'b.json' assert problem.line == 2
def test_no_changed_files_ignore(app, caplog): diff = """diff --git a/removed_file b/removed_file deleted file mode 100644 index 1f38447..0000000 --- a/removed_file +++ /dev/null @@ -1,3 +0,0 @@ -This content shouldn't be here. - -This file will be removed. """ context = TestContext('deepanalyzer/badwolf', None, 'pullrequest', 'message', {'commit': { 'hash': '000000' }}, {'commit': { 'hash': '111111' }}, pr_id=1) spec = Specification() spec.linters.append(ObjectDict(name='flake8', pattern=None)) lint = LintProcessor(context, spec, '/tmp') patch = PatchSet(diff.split('\n')) with mock.patch.object(lint, 'load_changes') as load_changes: load_changes.return_value = patch lint.process() assert load_changes.called assert 'No changed files found' in caplog.text()
def test_parse_file_multi_list(): s = """script: - ls - ps dockerfile: MyDockerfile service: - redis-server - postgresql after_success: - pwd - rm after_failure: - echo - exit notification: email: - [email protected] - [email protected]""" f = io.StringIO(s) spec = Specification.parse_file(f) assert spec.dockerfile == 'MyDockerfile' assert spec.services == ['redis-server', 'postgresql'] assert spec.scripts == ['ls', 'ps'] assert spec.after_success == ['pwd', 'rm'] assert spec.after_failure == ['echo', 'exit'] assert spec.notification.emails == [ '*****@*****.**', '*****@*****.**' ]
def test_flake8_lint_a_py(app, pr_context): diff = """diff --git a/a.py b/a.py new file mode 100644 index 0000000..fdeea15 --- /dev/null +++ b/a.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, unicode_literals + + +def add(a, b): + return a+ b """ spec = Specification() spec.linters.append(ObjectDict(name='flake8', pattern=None)) lint = LintProcessor(pr_context, spec, os.path.join(FIXTURES_PATH, 'flake8')) patch = PatchSet(diff.split('\n')) with mock.patch.object(lint, 'load_changes') as load_changes,\ mock.patch.object(lint, 'update_build_status') as build_status,\ mock.patch.object(lint, '_report') as report: load_changes.return_value = patch build_status.return_value = None report.return_value = (1, 2) lint.problems.set_changes(patch) lint.process() assert load_changes.called assert len(lint.problems) == 1 problem = lint.problems[0] assert problem.filename == 'a.py' assert problem.line == 6
def test_hadolint_lint_a_dockerfile(app, pr_context): diff = """diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cd19857 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,3 @@ +FROM ubuntu:16.04 + +RUN apt-get update && apt-get install file """ spec = Specification() spec.linters.append(ObjectDict(name='hadolint', pattern=None)) lint = LintProcessor(pr_context, spec, os.path.join(FIXTURES_PATH, 'hadolint')) patch = PatchSet(diff.split('\n')) with mock.patch.object(lint, 'load_changes') as load_changes,\ mock.patch.object(lint, 'update_build_status') as build_status,\ mock.patch.object(lint, '_report') as report: load_changes.return_value = patch build_status.return_value = None report.return_value = (1, 2) lint.problems.set_changes(patch) lint.process() assert load_changes.called assert len(lint.problems) == 4 problem = lint.problems[0] assert problem.line == 3 assert problem.filename == 'Dockerfile'
def test_stylelint_lint_a_scss(app, pr_context): diff = """diff --git a/a.scss b/a.scss new file mode 100644 index 0000000..e545209 --- /dev/null +++ b/a.scss @@ -0,0 +1 @@ +a[id="foo"] { content: "x"; } """ spec = Specification() spec.linters.append(ObjectDict(name='stylelint', pattern=None)) lint = LintProcessor(pr_context, spec, os.path.join(FIXTURES_PATH, 'stylelint')) patch = PatchSet(diff.split('\n')) with mock.patch.object(lint, 'load_changes') as load_changes,\ mock.patch.object(lint, 'update_build_status') as build_status,\ mock.patch.object(lint, '_report') as report: load_changes.return_value = patch build_status.return_value = None report.return_value = (1, 2) lint.problems.set_changes(patch) lint.process() assert load_changes.called assert len(lint.problems) == 2 problem = lint.problems[0] assert problem.filename == 'a.scss'
def test_rstlint_a_rst(app, pr_context): diff = """diff --git a/a.rst b/a.rst new file mode 100644 index 0000000..4e46cf9 --- /dev/null +++ b/a.rst @@ -0,0 +1,2 @@ +Hello World +==== """ spec = Specification() spec.linters.append(ObjectDict(name='rstlint')) lint = LintProcessor(pr_context, spec, os.path.join(FIXTURES_PATH, 'rstlint')) patch = PatchSet(diff.split('\n')) with mock.patch.object(lint, 'load_changes') as load_changes,\ mock.patch.object(lint, 'update_build_status') as build_status,\ mock.patch.object(lint, '_report') as report: load_changes.return_value = patch build_status.return_value = None report.return_value = (1, 2) lint.problems.set_changes(patch) lint.process() assert load_changes.called assert len(lint.problems) == 1 problem = lint.problems[0] assert problem.filename == 'a.rst' assert problem.line == 2
def test_bandit_lint_a_py(app, pr_context): diff = """diff --git a/a.py b/a.py new file mode 100644 index 0000000..719cd56 --- /dev/null +++ b/a.py @@ -0,0 +1,4 @@ +try: + a = 1 +except Exception: + pass """ spec = Specification() spec.linters.append(ObjectDict(name='bandit')) lint = LintProcessor(pr_context, spec, os.path.join(FIXTURES_PATH, 'bandit')) patch = PatchSet(diff.split('\n')) with mock.patch.object(lint, 'load_changes') as load_changes,\ mock.patch.object(lint, 'update_build_status') as build_status,\ mock.patch.object(lint, '_report') as report: load_changes.return_value = patch build_status.return_value = None report.return_value = (1, 2) lint.problems.set_changes(patch) lint.process() assert load_changes.called assert len(lint.problems) == 1 problem = lint.problems[0] assert problem.filename == 'a.py' assert problem.line == 3 assert not problem.is_error
def test_yamllint_a_yml(app, pr_context): diff = """diff --git a/a.yml b/a.yml new file mode 100644 index 0000000..1eccee8 --- /dev/null +++ b/a.yml @@ -0,0 +1,3 @@ +--- +a: 1 +a: 2 """ spec = Specification() spec.linters.append(ObjectDict(name='yamllint', pattern=None)) lint = LintProcessor(pr_context, spec, os.path.join(FIXTURES_PATH, 'yamllint')) patch = PatchSet(diff.split('\n')) with mock.patch.object(lint, 'load_changes') as load_changes,\ mock.patch.object(lint, 'update_build_status') as build_status,\ mock.patch.object(lint, '_report') as report: load_changes.return_value = patch build_status.return_value = None report.return_value = (1, 2) lint.problems.set_changes(patch) lint.process() assert load_changes.called assert len(lint.problems) == 1 problem = lint.problems[0] assert problem.filename == 'a.yml' assert problem.line == 3
def test_shellcheck_a_sh(app, pr_context): diff = """diff --git a/a.sh b/a.sh new file mode 100644 index 0000000..9fb9840 --- /dev/null +++ b/a.sh @@ -0,0 +2 @@ +#!/bin/sh +$foo=42 """ spec = Specification() spec.linters.append(ObjectDict(name='shellcheck', pattern=None)) lint = LintProcessor(pr_context, spec, os.path.join(FIXTURES_PATH, 'shellcheck')) patch = PatchSet(diff.split('\n')) with mock.patch.object(lint, 'load_changes') as load_changes,\ mock.patch.object(lint, 'update_build_status') as build_status,\ mock.patch.object(lint, '_report') as report: load_changes.return_value = patch build_status.return_value = None report.return_value = (1, 2) lint.problems.set_changes(patch) lint.process() assert load_changes.called assert len(lint.problems) > 0 problem = lint.problems[0] assert problem.filename == 'a.sh' assert problem.line == 2
def test_jsonlint_a_json_changes_out_of_range(app, pr_context): diff = """diff --git a/c.json b/c.json index 9b90002..c36a2a4 100644 --- a/c.json +++ b/c.json @@ -3,4 +3,5 @@ "b": 2, c: 3, d: 4 + e: 5 } """ spec = Specification() spec.linters.append(ObjectDict(name='jsonlint', pattern=None)) lint = LintProcessor(pr_context, spec, os.path.join(FIXTURES_PATH, 'jsonlint')) patch = PatchSet(diff.split('\n')) with mock.patch.object(lint, 'load_changes') as load_changes,\ mock.patch.object(lint, 'update_build_status') as build_status,\ mock.patch.object(lint, '_report') as report: load_changes.return_value = patch build_status.return_value = None report.return_value = (1, 2) lint.problems.set_changes(patch) lint.process() assert load_changes.called assert len(lint.problems) == 0
def test_mypy_lint_a_py(app, pr_context): diff = """diff --git a/a.py b/a.py new file mode 100644 index 0000000..87604af --- /dev/null +++ b/a.py @@ -0,0 +1,5 @@ +def p() -> None: + print('hello') + + +a = p() """ spec = Specification() spec.linters.append(ObjectDict(name='mypy', pattern=None)) lint = LintProcessor(pr_context, spec, os.path.join(FIXTURES_PATH, 'mypy')) patch = PatchSet(diff.split('\n')) with mock.patch.object(lint, 'load_changes') as load_changes,\ mock.patch.object(lint, 'update_build_status') as build_status,\ mock.patch.object(lint, '_report') as report: load_changes.return_value = patch build_status.return_value = None report.return_value = (1, 2) lint.problems.set_changes(patch) lint.process() assert load_changes.called assert len(lint.problems) == 1 problem = lint.problems[0] assert problem.line == 5 assert problem.filename == 'a.py'
def parse_spec(self): '''Parse repository build/lint spec''' logger.info('Parsing specification for repository %s', self.context.repository) conf_file = os.path.join(self.context.clone_path, current_app.config['BADWOLF_PROJECT_CONF']) try: spec = Specification.parse_file(conf_file) except OSError: logger.warning( 'No project configuration file found for repo: %s', self.context.repository ) raise SpecificationNotFound() secretfile = os.path.join(self.context.clone_path, 'Secretfile') if os.path.exists(secretfile): spec.parse_secretfile(secretfile) branch = self.context.source['branch']['name'] if self.context.type == 'branch' and not spec.is_branch_enabled(branch): logger.info( 'Ignore tests since branch %s test is not enabled. Allowed branches: %s', branch, spec.branch ) raise BuildDisabled() if not spec.scripts and not spec.linters: logger.warning('No script(s) or linter(s) to run') raise InvalidSpecification('No script or linter to run') self.spec = spec # setup Vault vault_url = spec.vault.url or current_app.config['VAULT_URL'] vault_token = spec.vault.token or current_app.config['VAULT_TOKEN'] if vault_url and vault_token: self.vault = hvac.Client(url=vault_url, token=vault_token) self._populate_envvars_from_vault()
def test_parse_file_single_list(app): s = """script: - ls dockerfile: MyDockerfile service: - redis-server after_success: - pwd after_failure: - exit notification: email: - [email protected] slack_webhook: - https://1""" f = io.StringIO(s) spec = Specification.parse_file(f) assert spec.dockerfile == 'MyDockerfile' assert spec.services == ['redis-server'] assert spec.scripts == ['ls'] assert spec.after_success == ['pwd'] assert spec.after_failure == ['exit'] assert spec.notification.email.recipients == ['*****@*****.**'] assert spec.notification.email.on_success == 'never' assert spec.notification.email.on_failure == 'always' assert spec.notification.slack_webhook.webhooks == ['https://1'] assert spec.notification.slack_webhook.on_success == 'always' assert spec.notification.slack_webhook.on_failure == 'always'
def test_parse_single_string_conf(app): spec = Specification.parse({ 'service': 'redis-server', 'script': 'ls', 'after_success': 'pwd', 'after_failure': 'exit', 'notification': { 'email': { 'recipients': '*****@*****.**', 'on_success': 'always', }, 'slack_webhook': { 'webhooks': ['https://1', 'https://2'], 'on_failure': 'never', } } }) assert spec.services == ['redis-server'] assert spec.scripts == ['ls'] assert spec.after_success == ['pwd'] assert spec.after_failure == ['exit'] assert spec.notification.email.recipients == ['*****@*****.**'] assert spec.notification.email.on_success == 'always' assert spec.notification.email.on_failure == 'always' assert len(spec.notification.slack_webhook.webhooks) == 2 assert spec.notification.slack_webhook.on_success == 'always' assert spec.notification.slack_webhook.on_failure == 'never'
def parse_spec(self): '''Parse repository build/lint spec''' logger.info('Parsing specification for repository %s', self.context.repository) conf_file = os.path.join(self.context.clone_path, current_app.config['BADWOLF_PROJECT_CONF']) try: spec = Specification.parse_file(conf_file) except OSError: logger.warning('No project configuration file found for repo: %s', self.context.repository) raise SpecificationNotFound() secretfile = os.path.join(self.context.clone_path, 'Secretfile') if os.path.exists(secretfile): spec.parse_secretfile(secretfile) branch = self.context.source['branch']['name'] if self.context.type == 'branch' and not spec.is_branch_enabled( branch): logger.info( 'Ignore tests since branch %s test is not enabled. Allowed branches: %s', branch, spec.branch) raise BuildDisabled() if not spec.scripts and not spec.linters: logger.warning('No script(s) or linter(s) to run') raise InvalidSpecification('No script or linter to run') self.spec = spec
def test_parse_linter_with_regex_pattern(app): s = """linter: {name: "flake8", pattern: '.*\.(sls|yml|yaml)$'}""" f = io.StringIO(s) spec = Specification.parse_file(f) assert len(spec.linters) == 1 linter0 = spec.linters[0] assert linter0.name == 'flake8' assert linter0.pattern == '.*\.(sls|yml|yaml)$'
def test_parse_simple_linter(app): s = """linter: flake8""" f = io.StringIO(s) spec = Specification.parse_file(f) assert len(spec.linters) == 1 linter0 = spec.linters[0] assert linter0.name == 'flake8' assert linter0.pattern is None
def test_parse_empty_conf(): spec = Specification.parse({}) assert len(spec.scripts) == 0 assert len(spec.services) == 0 assert len(spec.after_success) == 0 assert len(spec.after_failure) == 0 assert len(spec.branch) == 0 assert spec.dockerfile == 'Dockerfile' assert len(spec.notification.emails) == 0
def test_parse_linter_with_pattern(app): s = """linter: {name: "flake8", pattern: "*.py", whatever: 123}""" f = io.StringIO(s) spec = Specification.parse_file(f) assert len(spec.linters) == 1 linter0 = spec.linters[0] assert linter0.name == 'flake8' assert linter0.pattern == '*.py' assert linter0.whatever == 123
def test_parse_env_single_string(app): s = "env: X=1 Y=2 Z=3\n" f = io.StringIO(s) spec = Specification.parse_file(f) assert len(spec.environments) == 1 env0 = spec.environments[0] assert env0['X'] == '1' assert env0['Y'] == '2' assert env0['Z'] == '3'
def test_parse_artifacts_paths(app): s = '''artifacts: paths: - dist excludes: __pycache__ ''' f = io.StringIO(s) spec = Specification.parse_file(f) assert spec.artifacts.paths == ['dist'] assert spec.artifacts.excludes == ['__pycache__']
def test_parse_vault_no_env(app): s = '''vault: url: http://localhost:8200 token: abc123 ''' f = io.StringIO(s) spec = Specification.parse_file(f) assert spec.vault.url == 'http://localhost:8200' assert spec.vault.token == 'abc123' assert not spec.vault.env
def test_parse_secure_env(app): s = """env: - secure: {}""".format(to_text(SecureToken.encrypt('X=1 Y=2 Z=3'))) f = io.StringIO(s) spec = Specification.parse_file(f) assert len(spec.environments) == 1 env0 = spec.environments[0] assert env0['X'] == '1' assert env0['Y'] == '2' assert env0['Z'] == '3'
def test_load_changes_failed_ignore(app, pr_context, caplog): spec = Specification() spec.linters.append('flake8') lint = LintProcessor(pr_context, spec, '/tmp') with mock.patch.object(lint, 'load_changes') as load_changes: load_changes.return_value = None lint.process() assert load_changes.called assert 'Load changes failed' in caplog.text