166 lines
5.5 KiB
Python
166 lines
5.5 KiB
Python
# Copyright 2018 Mycroft AI Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
#
|
|
"""Execute Mycroft Skill intent testing
|
|
|
|
This standalone tester can be invoked by a skill developer from
|
|
within the Mycroft venv by using the command:
|
|
python -m test.integrationtests.skills.runner
|
|
This will test the skill the same way as discover_test.py used
|
|
for automatic integration tests, but will run only the skill
|
|
developer's tests.
|
|
"""
|
|
|
|
import glob
|
|
import unittest
|
|
import os
|
|
from os.path import exists
|
|
import sys
|
|
import importlib
|
|
import argparse
|
|
from test.integrationtests.skills.skill_tester import MockSkillsLoader
|
|
from test.integrationtests.skills.skill_tester import SkillTest
|
|
|
|
|
|
desc = "Standalone test utility for Mycroft Skills. This will execute the " \
|
|
"tests defined under the Skill's test/intent folder. For more " \
|
|
"information on creating tests, see: " \
|
|
"https://mycroft.ai/documentation/skills/automatic-testing/"
|
|
|
|
# Get path to skill(s) to test from command line, default to cwd
|
|
parser = argparse.ArgumentParser(description=desc)
|
|
parser.add_argument("skill_path", nargs='?', default=os.getcwd(),
|
|
help="path to skill to test, default=current")
|
|
args = parser.parse_args()
|
|
HOME_DIR = os.path.dirname(args.skill_path + '/')
|
|
sys.argv = sys.argv[:1]
|
|
|
|
|
|
def load_test_environment(skill):
|
|
"""Load skill's test environment if present
|
|
|
|
Args:
|
|
skill (str): path to skill root folder
|
|
|
|
Returns:
|
|
Module if a valid test environment module was found else None
|
|
"""
|
|
test_env = None
|
|
test_env_path = os.path.join(skill, 'test/__init__.py')
|
|
if exists(test_env_path):
|
|
skill_env = skill + '.test_env'
|
|
spec = importlib.util.spec_from_file_location(skill_env, test_env_path)
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[skill_env] = module
|
|
spec.loader.exec_module(module)
|
|
if (hasattr(module, 'test_runner') and
|
|
callable(module.test_runner) or
|
|
hasattr(module, 'test_setup') and
|
|
callable(module.test_setup)):
|
|
test_env = module
|
|
return test_env
|
|
|
|
|
|
def discover_tests():
|
|
"""Find skills with test files
|
|
|
|
For all skills with test files, starten from current directory,
|
|
find the test files in subdirectory test/intent.
|
|
|
|
Returns:
|
|
Test case files found along with test environment script if available.
|
|
"""
|
|
|
|
tests = {}
|
|
test_envs = {}
|
|
|
|
skills = [HOME_DIR]
|
|
|
|
for skill in skills:
|
|
print("Searching for tests under: " +
|
|
os.path.join(skill, "test/intent/*.json"))
|
|
test_intent_files = [
|
|
f for f
|
|
in sorted(
|
|
glob.glob(os.path.join(skill, 'test/intent/*.json')))
|
|
]
|
|
if len(test_intent_files) > 0:
|
|
tests[skill] = test_intent_files
|
|
|
|
# Load test environment script
|
|
test_env = load_test_environment(skill)
|
|
test_envs[skill] = test_env
|
|
|
|
return tests, test_envs
|
|
|
|
|
|
class IntentTestSequenceMeta(type):
|
|
def __new__(mcs, name, bases, d):
|
|
def gen_test(a, b, test_env):
|
|
def test(self):
|
|
t = SkillTest(a, b, self.emitter)
|
|
if not t.run(self.loader):
|
|
assert False, "Failure: " + t.failure_msg
|
|
|
|
def test_env_test(self):
|
|
assert test_env.test_runner(a, b, self.emitter, self.loader)
|
|
|
|
if test_env and hasattr(test_env, 'test_runner'):
|
|
return test_env_test
|
|
else:
|
|
return test
|
|
tests, test_envs = discover_tests()
|
|
mcs.tests, mcs.test_envs = tests, test_envs
|
|
|
|
for skill in tests.keys():
|
|
skill_name = os.path.basename(skill) # Path of the skill
|
|
for example in tests[skill]:
|
|
# Name of the intent
|
|
test_filename = os.path.basename(example)
|
|
test_name = "test_Intent[%s:%s]" % (skill_name,
|
|
test_filename)
|
|
test_env = test_envs[skill]
|
|
d[test_name] = gen_test(skill, example, test_env)
|
|
|
|
return type.__new__(mcs, name, bases, d)
|
|
|
|
|
|
class IntentTestSequence(unittest.TestCase, metaclass=IntentTestSequenceMeta):
|
|
"""This is the TestCase class that Python's unit tester can execute.
|
|
"""
|
|
loader = None
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.loader = MockSkillsLoader(HOME_DIR)
|
|
cls.emitter = cls.loader.load_skills()
|
|
|
|
# Run test setup provided by the test environment
|
|
for s in cls.loader.skills:
|
|
if (s.root_dir in cls.test_envs and
|
|
hasattr(cls.test_envs[s.root_dir], 'test_setup')):
|
|
try:
|
|
cls.test_envs[s.root_dir].test_setup(s)
|
|
except Exception as e:
|
|
print('test_setup for {} failed: {}'.format(s.name,
|
|
repr(e)))
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
cls.loader.unload_skills()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|