Skip to content

Commit

Permalink
Implement tests using unittest completely
Browse files Browse the repository at this point in the history
For some reason `nosetests` doesn't run the generated tests so I changed the build command for travis
  • Loading branch information
FichteFoll committed Aug 13, 2013
1 parent c20ea11 commit e394f46
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 88 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
language: python
python:
- "3.3"

#command to run tests
script: nosetests
script: python -m unittest
251 changes: 164 additions & 87 deletions tests/test.py
Original file line number Diff line number Diff line change
@@ -1,117 +1,105 @@
#!/usr/bin/env python3

"""Tests for the validity of the channel file
"""Tests for the validity of the channel and repository files.
You can run this file directly or with `notetests` (or `python -m unittest`)
from the root directory.
You can run this script directly or with `python -m unittest` from this or the
root directory. For some reason `nosetests` does not pick up the generated tests
even though they are generated at load time.
"""

import os
import json
import unittest
from collections import OrderedDict
from nose.tools import assert_equal, assert_in, assert_not_in, assert_regexp_matches
from functools import wraps

# Generator tests can't be part of a class, so for consistency
# they are all functions
################################################################################

def test_channel():
with open("channel.json") as fp:
data = json.load(fp)
keys = sorted(data.keys())
assert_equal(keys, ['repositories', 'schema_version'])
def _open(filepath, *args, **kwargs):
"""Wrapper function that can search one dir above if the desired file
does not exist.
"""
if not os.path.exists(filepath):
filepath = os.path.join("..", filepath)

assert_equal(data['schema_version'], '2.0')
assert_equal(type(data['repositories']), list)
return open(filepath, *args, **kwargs)

for repository in data['repositories']:
assert_equal(type(repository), str)

def generator_class(cls):
"""Class decorator for class that use generating methods.
def test_repository():
with open('repository.json') as f:
data = json.load(f, object_pairs_hook=OrderedDict)
A class that is decorated with this function will be searched for methods
starting with "generate_" (similar to "test_") and then run like a nosetest
generator.
Note: The generator function must be a classmethod!
keys = sorted(data.keys())
assert_equal(keys, ['includes', 'packages', 'schema_version'])
Generate tests using the following statement:
yield function, (arg1, arg2, arg3) # ...
"""
for name in list(cls.__dict__.keys()):
if not name.startswith("generate_") or not callable(getattr(cls, name)):
continue

assert_equal(data['schema_version'], '2.0')
assert_equal(data['packages'], [])
assert_equal(type(data['includes']), list)
# Create new methods for each `yield`
for sub_call in getattr(cls, name)():
method, params = sub_call

for include in data['includes']:
assert_equal(type(include), str)
@wraps(method)
def wrapper(self, method=method, params=params):
return method(self, *params)

# Do not attempt to print lists/dicts with printed lenght of 1000 or
# more, they are not interesting for us (probably the whole file)
args = []
for v in params:
string = repr(v)
if len(string) > 1000:
args.append('...')
else:
args.append(repr(v))
name = "%s(%s)" % (method.__name__.replace("_test", "test"),
", ".join(args))
setattr(cls, name, wrapper)

def test_repository_includes():
with open('repository.json') as f:
data = json.load(f, object_pairs_hook=OrderedDict)
# Remove the generator afterwards, it did its work
delattr(cls, name)

for include in data['includes']:
yield check_include, include
# print(dir(cls))
return cls

with open(include) as f:
include_data = json.load(f, object_pairs_hook=OrderedDict)
for package in include_data['packages']:
yield check_package, package
if 'releases' in package:
for release in package['releases']:
yield check_release, package, release

def get_package_name(data):
"""Gets "name" from a package with a workaround when it's not defined.
def check_include(filename):
with open(filename) as f:
data = json.load(f, object_pairs_hook=OrderedDict)
keys = sorted(data.keys())
assert_equal(keys, ['packages', 'schema_version'])
assert_equal(data['schema_version'], '2.0')
assert_equal(type(data['packages']), list)
Use the last part of details url for the package's name otherwise since
packages must one of these two keys anyway.
"""
return data.get('name', data.get('details', '').rsplit('/', 1)[-1])


def check_package(data):
for key in data.keys():
assert_in(key, ['name', 'details', 'releases', 'homepage', 'author',
'readme', 'issues', 'donate', 'buy', 'previous_names', 'labels'])
assert_equal(type(data[key]), map_key_type(key))
if key in ['details', 'homepage', 'readme', 'issues', 'donate', 'buy']:
assert_regexp_matches(data[key], '^https?://')
################################################################################

if 'details' not in data:
assert_in('name', data, 'The key "name" is required if no "details" URL provided')
assert_in('homepage', data, 'The key "homepage" is required if no "details" URL provided')
assert_in('author', data, 'The key "author" is required if no "details" URL provided')
assert_in('releases', data, 'The key "releases" is required if no "details" URL provided')
class ChannelTests(unittest.TestCase):
with _open('channel.json') as f:
j = json.load(f)

def test_channel_keys(self):
keys = sorted(self.j.keys())
self.assertEqual(keys, ['repositories', 'schema_version'])

def check_release(package, data):
for key in data.keys():
assert_not_in(key, ['version', 'date', 'url'], 'The version, date and ' + \
'url keys should not be used in the main repository since a pull ' + \
'request would be necessary for every release')
self.assertEqual(self.j['schema_version'], '2.0')
self.assertIsInstance(self.j['repositories'], list)

assert_in(key, ['details', 'sublime_text', 'platforms'])
for repo in self.j['repositories']:
self.assertIsInstance(repo, str)

if key in ['details', 'url']:
assert_regexp_matches(data[key], '^https?://')

if key == 'sublime_text':
assert_regexp_matches(data[key], '^(\*|<=?\d{4}|>=?\d{4})$')
@generator_class
class RepositoryTests(unittest.TestCase):
with _open('repository.json') as f:
j = json.load(f, object_pairs_hook=OrderedDict)

if key == 'platforms':
assert_in(type(data[key]), [str, list])
if type(data[key]) == str:
assert_in(data[key], ['*', 'osx', 'linux', 'windows'])
else:
for platform in data[key]:
assert_in(platform, ['*', 'osx', 'linux', 'windows'])

assert_in('details', data, 'A release must have a "details" key if it is in ' + \
'the main repository. For custom releases, a custom repository.json ' + \
'file must be hosted elsewhere.')


def map_key_type(key):
return {
key_types_map = {
'name': str,
'details': str,
'releases': list,
Expand All @@ -123,12 +111,101 @@ def map_key_type(key):
'buy': str,
'previous_names': list,
'labels': list
}.get(key)

}

def test_repository_keys(self):
keys = sorted(self.j.keys())
self.assertEqual(keys, ['includes', 'packages', 'schema_version'])

self.assertEqual(self.j['schema_version'], '2.0')
self.assertEqual(self.j['packages'], [])
self.assertIsInstance(self.j['includes'], list)

for include in self.j['includes']:
self.assertIsInstance(include, str)

@classmethod
def generate_include_tests(cls):
for include in cls.j['includes']:
try:
with _open(include) as f:
data = json.load(f, object_pairs_hook=OrderedDict)
except Exception as e:
print("adding failure")
yield cls._test_error, ("Error while reading %r" % include, e)
# print("Error while reading %r: %s" % (include, e))
continue

# `include` is for output during tests only
yield cls._test_include_keys, (include, data)

for package in data['packages']:
yield cls._test_package, (include, package)

package_name = get_package_name(data)

if 'releases' in package:
for release in package['releases']:
yield cls._test_release, (package_name, release)

def _test_include_keys(self, include, data):
keys = sorted(data.keys())
self.assertEqual(keys, ['packages', 'schema_version'])
self.assertEqual(data['schema_version'], '2.0')
self.assertIsInstance(data['packages'], list)

def _test_package(self, include, data):
for key in data.keys():
self.assertIn(key, ('name', 'details', 'releases', 'homepage',
'author', 'readme', 'issues', 'donate', 'buy',
'previous_names', 'labels'))
self.assertIsInstance(data[key], self.key_types_map[key])

if key in ('details', 'homepage', 'readme', 'issues', 'donate',
'buy'):
self.assertRegex(data[key], '^https?://')

if 'details' not in data:
for key in ('name', 'homepage', 'author', 'releases'):
self.assertIn(key, data, '%r is required if no "details" URL '
'provided' % key)

def _test_release(self, package_name, data):
# Fail early
self.assertIn('details', data,
'A release must have a "details" key if it is in the '
'main repository. For custom releases, a custom '
'repository.json file must be hosted elsewhere.')

for key in data.keys():
# Display this message despite it being tested with the next test
# anyway
self.assertNotIn(key, ('version', 'date', 'url'),
'The version, date and url keys should not be '
'used in the main repository since a pull request '
'would be necessary for every release')

self.assertIn(key, ('details', 'sublime_text', 'platforms'))

if key == 'details':
self.assertRegex(data[key], '^https?://')

if key == 'sublime_text':
self.assertRegex(data[key], '^(\*|<=?\d{4}|>=?\d{4})$')

if key == 'platforms':
self.assertIsInstance(data[key], (str, list))
if isinstance(data[key], str):
self.assertIn(data[key], ('*', 'osx', 'linux', 'windows'))
else:
for plat in data[key]:
self.assertIn(plat, ('*', 'osx', 'linux', 'windows'))

def _test_error(self, msg, e):
self.fail("%s: %r" % (msg, e))


################################################################################

if __name__ == '__main__':
# Manually go up one directory if this file is run explicitly
if not os.path.exists(repo_file):
repo_file = os.path.join("..", repo_file)

unittest.main()

0 comments on commit e394f46

Please sign in to comment.