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 test suite #295

Merged
merged 3 commits into from Apr 5, 2016
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

@@ -1 +1,2 @@
.vagrant/
__pycache__
@@ -20,23 +20,16 @@ matrix:
os: linux
sudo: required
dist: trusty
- env: SALT_NODE_ID=test # Not a Salt node, runs test suite instead
os: linux
sudo: required
dist: trusty
language: python
python: 3.5
allow_failures:
- env: SALT_NODE_ID=servo-linux-cross1

before_install:
- .travis/install_salt -F -c .travis -- "${TRAVIS_OS_NAME}"

install:
- .travis/setup_salt_roots

# For debugging, check the grains reported by the Travis builder
- sudo salt-call --id="${SALT_NODE_ID}" grains.items

script:
# Minimally validate YAML and Jinja at a basic level
- sudo salt-call --id="${SALT_NODE_ID}" --retcode-passthrough state.show_highstate
# Full on installation test
- sudo salt-call --id="${SALT_NODE_ID}" --retcode-passthrough --log-level=warning state.highstate
script: .travis/dispatch.sh

notifications:
webhooks: http://build.servo.org:54856/travis
@@ -0,0 +1,23 @@
#!/usr/bin/env sh

set -o errexit
set -o nounset

if [ "${SALT_NODE_ID}" = "test" ]; then
# Using .travis.yml to specify Python 3.5 to be preinstalled, just to check
printf "Using $(python3 --version) at $(which python3)\n"

# Run test suite separately for parallelism
./test.py
else
.travis/install_salt.sh -F -c .travis -- "${TRAVIS_OS_NAME}"
.travis/setup_salt_roots.sh

# For debugging, check the grains reported by the Travis builder
sudo salt-call --id="${SALT_NODE_ID}" grains.items

# Minimally validate YAML and Jinja at a basic level
sudo salt-call --id="${SALT_NODE_ID}" --retcode-passthrough state.show_highstate
# Full on installation test
sudo salt-call --id="${SALT_NODE_ID}" --retcode-passthrough --log-level=warning state.highstate
fi
File renamed without changes.
File renamed without changes.
@@ -2,7 +2,8 @@

Style guide for Salt states (and other code) in this repo. Unfortunately,
no linter exists yet for Salt states, so there is no automated way to
check for compliance with this guide.
check for compliance with this guide. However, there is a small test suite
for a few items; it can be run with `./test.py`.

## General

0 buildbot/github_buildbot.py 100755 → 100644
No changes.
@@ -60,5 +60,5 @@ remove-old-build-logs:
cron.present:
- name: 'find /home/servo/buildbot/master/*/*.bz2 -mtime +5 -delete'
- user: root
- minute: 1
- minute: 1
- hour: 0
41 test.py
@@ -0,0 +1,41 @@
#!/usr/bin/env python3

import importlib
import os
import sys

from tests.util import color, GREEN, RED, Failure, project_path


def is_python_script(path):
return path.endswith('.py')


def main():
ANY_FAILURES = False

test_dir = os.path.join(project_path(), 'tests')
tests = sorted(filter(is_python_script, os.listdir(test_dir)))
for test in tests:
test_mod = importlib.import_module('tests.{}'.format(test[:-3]))
if not hasattr(test_mod, 'run'): # Not a test script
continue

try:
result = test_mod.run()
except Exception as e:
result = Failure('Test \'{}\' raised an exception:'.format(test),
str(e))

if result.is_success():
print('[ {} ] {}'.format(color(GREEN, 'PASS'), result.message))
else:
ANY_FAILURES = True
print('[ {} ] {}'.format(color(RED, 'FAIL'), result.message))
for line in result.output.splitlines():
print(' {}'.format(line))

return 1 if ANY_FAILURES else 0

if __name__ == '__main__':
sys.exit(main())
No changes.
@@ -0,0 +1,44 @@
import os
import stat

from .util import display_path, paths, Failure, Success


SHEBANG = """\
#!/usr/bin/env {}
"""

INTERPRETERS = {
'py': 'python3',
'sh': 'sh'
}


def is_executable(path):
exec_bit = stat.S_IXUSR
return exec_bit == (exec_bit & os.stat(path)[stat.ST_MODE])


def has_correct_header(path):
extension = path.rpartition('.')[2]
expected_header = SHEBANG.format(INTERPRETERS[extension])

with open(path, 'r', encoding='utf-8') as file_to_check:
header = file_to_check.read(len(expected_header))

if header != expected_header:
return False

return True


def run():
executables = filter(is_executable, paths())
failures = list(filter(lambda e: not has_correct_header(e), executables))

if len(failures) == 0:
return Success("All executable shebangs are correct")
else:
output = '\n'.join([display_path(path) for path in failures])
return Failure("Bad shebangs found in these files:", output)
@@ -0,0 +1,42 @@
import itertools
import re

from .util import colon, color, display_path, paths, Failure, Success


def display_trailing_whitespace(whitespace):
# Show trailing whitespace with printing characters and in red
# To make it easy to see what needs to be removed
replaced = whitespace.replace(' ', '-').replace('\t', r'\t')
return color(31, replaced)


def display_failure(failure):
path, line_number, match = failure
line = match.group(1) + display_trailing_whitespace(match.group(2))
return display_path(path) + colon() + str(line_number) + colon() + line


def check_whitespace(path):
CHECK_REGEX = re.compile(r'(.*?)(\s+)$')
trailing_whitespace = []
with open(path, 'r', encoding='utf-8') as file_to_check:
try:
for line_number, line in enumerate(file_to_check):
line = line.rstrip('\r\n')
match = CHECK_REGEX.match(line)
if match is not None:
trailing_whitespace.append((path, line_number, match))
except UnicodeDecodeError:
pass # Not a text (UTF-8) file
return trailing_whitespace


def run():
failures = list(itertools.chain(*map(check_whitespace, paths())))

if len(failures) == 0:
return Success("No trailing whitespace found")
else:
output = '\n'.join([display_failure(failure) for failure in failures])
return Failure("Trailing whitespace found on files and lines:", output)
@@ -0,0 +1,65 @@
import os

RED = 31
GREEN = 32
BLUE = 34
MAGENTA = 35


def color(code, string):
return '\033[' + str(code) + 'm' + string + '\033[0m'


def display_path(path):
return color(MAGENTA, path)


def colon():
return color(BLUE, ':')


EXCLUDE_DIRS = ['.git', '.vagrant']


def project_path():
# One dirname for tests dir, another for project dir
project_dir = os.path.dirname(os.path.dirname(__file__))
common = os.path.commonpath([project_dir, os.getcwd()])
return project_dir.replace(common, '.', 1) # Only replace once


def paths():
for root, dirs, files in os.walk(project_path(), topdown=True):
for exclude_dir in EXCLUDE_DIRS:
if exclude_dir in dirs:
dirs.remove(exclude_dir)

for filename in files:
yield os.path.join(root, filename)


class TestResult(object):
pass


class Success(TestResult):
def __init__(self, message):
self.message = message

def is_success(self):
return True

def is_failure(self):
return False


class Failure(TestResult):
def __init__(self, message, output):
self.message = message
self.output = output

def is_success(self):
return False

def is_failure(self):
return True
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.