diff --git a/test/jstests/__init__.py b/test/jstests/__init__.py index 686ff7f18..30d55e92a 100644 --- a/test/jstests/__init__.py +++ b/test/jstests/__init__.py @@ -1,47 +1,63 @@ -from . import ( - array_access, - assignments, - basic, - branch, - calc, - call, - comments, - debug, - do_loop, - empty_return, - for_empty, - for_in, - for_loop, - func_expr, - getfield, - label, - morespace, - object_literal, - operators, - parens, - precedence, - strange_chars, - switch, - try_statement, - unary, - unshift, - while_loop, - with_statement -) +""" +This package contains templates for `test_jsinterp` and `test_interp_parse` to create test methods. +These modules will create a test method for each module in this package. A test method consist of one or more subtest. +Each subtest initializes an instance of the tested class and runs one or more assertion. +Any module should have a `list` of `dict` named ``tests`` and optionally a `dict` named ``skip``. -modules = [array_access, assignments, basic, branch, calc, call, comments, debug, do_loop, empty_return, for_empty, - for_in, for_loop, func_expr, getfield, label, morespace, object_literal, operators, parens, precedence, - strange_chars, switch, try_statement, unary, unshift, while_loop, with_statement] +Each `dict` in ``tests`` may have the following keys: + + code: If missing subtest is skipped, Otherwise it's value is used as code to initialize the tested class. + globals: Optional. Used only by `test_jsinterp`. If set used as argument `variables` initializing `JSInterperter`. + asserts: Used only by `test_jsinterp`. If this is missing subtest is skipped, Should be a list of `dict`, each used + as an assertion for the initialized `JSInterpreter`. Each `dict` may have the following keys: + value: If missing assertion is skipped. Otherwise it's value is used as expected value in + an `assertEqual` call. + call: Optional. If set used as arguments of a `call_function` call of the initialized `JSInterpreter` + and the actual value of the created `assertEqual` call will be the return value of it. + Otherwise the actual value will be the return value of the `run` call. + ast: Used only by `test_interp_parse`. If missing subtest is skipped, Otherwise it's value is used as + expected value in an `assertEqual` call. The actual value will be the return value of the `parse` call + converted to `list`. Both on expected anc actual value `traverse` is called first to flatten and handle `zip` + objects. + +In the `dict` named ``skip`` is optional and may have the following keys: + interpret + parse +Both used as the argument of `skipTest` decorator of the created test method in `test_jsinterp` +and `test_jsinterp_parse` respectably. Unless they're value is `True`, that case the test method is skipped entirely, +or `False`, which is the default value. + +Example: + This is not a functional template, rather a skeleton: + + skip = {'interpret': 'Test not yet implemented', + 'parse': 'Test not yet implemented'} + + tests = [ + { + 'code': '', + 'globals': {}, + 'asserts': [{'value': 0, 'call': ('f',)}], + 'ast': [] + } + ] +""" def gettestcases(): - for module in modules: + import os + + modules = [module[:-3] for module in os.listdir(os.path.dirname(__file__)) + if module != '__init__.py' and module[-3:] == '.py'] + me = __import__(__name__, globals(), locals(), modules) + + for module_name in modules: + module = getattr(me, module_name) if hasattr(module, 'tests'): - case = {'name': module.__name__[len(__name__) + 1:], 'subtests': [], 'skip': {}} - for test in getattr(module, 'tests'): - if 'code' in test: - case['subtests'].append(test) - if hasattr(module, 'skip'): - case['skip'] = getattr(module, 'skip') + case = { + 'name': module.__name__[len(__name__) + 1:], + 'subtests': module.tests, + 'skip': getattr(module, 'skip', {}) + } yield case diff --git a/test/jstests/call.py b/test/jstests/call.py index ac0fdbb94..e8ff330c6 100644 --- a/test/jstests/call.py +++ b/test/jstests/call.py @@ -44,7 +44,7 @@ tests = [ ] }, { 'code': 'function x(a) { return a.split(""); }', - # built-in functions not yet implemented + # FIXME built-in functions not yet implemented # 'asserts': [{'value': ["a", "b", "c"], 'call': ('x',"abc")}], 'ast': [ (Token.FUNC, 'x', ['a'], [ diff --git a/test/test_jsinterp.py b/test/test_jsinterp.py index 36b6b7cb0..495f017ac 100644 --- a/test/test_jsinterp.py +++ b/test/test_jsinterp.py @@ -1,5 +1,9 @@ #!/usr/bin/env python +""" +see: `jstests` +""" + from __future__ import unicode_literals # Allow direct execution @@ -14,7 +18,7 @@ else: sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from youtube_dl.jsinterp import JSInterpreter -from test.jstests import gettestcases +from .jstests import gettestcases defs = gettestcases() # set level to logging.DEBUG to see messages about missing assertions @@ -29,15 +33,25 @@ class TestJSInterpreter(unittest.TestCase): def generator(test_case, name): def test_template(self): for test in test_case['subtests']: - jsi = JSInterpreter(test['code'], variables=test.get('globals')) - if 'asserts' in test: - for a in test['asserts']: - if 'call' in a: - self.assertEqual(jsi.call_function(*a['call']), a['value']) - else: - self.assertEqual(jsi.run(), a['value']) + if 'code' not in test: + log_reason = 'No code in subtest, skipping' + elif 'asserts' not in test: + log_reason = 'No assertion in subtest, skipping' else: - log.debug('No assertion for subtest, skipping') + log_reason = None + + if log_reason is None: + jsi = JSInterpreter(test['code'], variables=test.get('globals')) + for a in test['asserts']: + if 'value' in a: + if 'call' in a: + self.assertEqual(jsi.call_function(*a['call']), a['value']) + else: + self.assertEqual(jsi.run(), a['value']) + else: + log.debug('No value in assertion, skipping') + else: + log.debug(log_reason) log = logging.getLogger('TestJSInterpreter.%s' % name) return test_template diff --git a/test/test_jsinterp_parse.py b/test/test_jsinterp_parse.py index f984d04d3..53c53e347 100644 --- a/test/test_jsinterp_parse.py +++ b/test/test_jsinterp_parse.py @@ -1,12 +1,16 @@ #!/usr/bin/env python +""" +see: `jstests` +""" + from __future__ import unicode_literals # Allow direct execution import os import sys -import logging import copy +import logging if sys.version_info < (2, 7): import unittest2 as unittest @@ -20,7 +24,7 @@ from .jstests import gettestcases def traverse(node, tree_types=(list, tuple)): if sys.version_info > (3,) and isinstance(node, zip): - node = list(copy.deepcopy(node)) + node = list(copy.copy(node)) if isinstance(node, tree_types): tree = [] for value in node: @@ -42,13 +46,16 @@ class TestJSInterpreterParse(unittest.TestCase): def generator(test_case, name): def test_template(self): - for a in test_case['subtests']: - jsp = Parser(a['code']) - parsed = list(jsp.parse()) - if 'ast' in a: - self.assertEqual(traverse(parsed), traverse(a['ast'])) + for test in test_case['subtests']: + if 'code' in test: + jsp = Parser(test['code']) + parsed = list(jsp.parse()) + if 'ast' in test: + self.assertEqual(traverse(parsed), traverse(test['ast'])) + else: + log.debug('No AST for subtest, trying to parse only') else: - log.debug('No AST for subtest, trying to parse only') + log.debug('No code in subtest, skipping') log = logging.getLogger('TestJSInterpreterParse.%s' % name) return test_template