Merge pull request #2104 from MycroftAI/feature/skill-gid

Feature/skill gid
pull/2130/head
Åke 2019-05-22 06:19:37 +02:00 committed by GitHub
commit 1300773f3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 269 additions and 233 deletions

View File

@ -6,6 +6,7 @@ before_install:
- sudo apt-get install -qq mpg123 portaudio19-dev libglib2.0-dev swig bison libtool autoconf libglib2.0-dev libicu-dev libfann-dev realpath - sudo apt-get install -qq mpg123 portaudio19-dev libglib2.0-dev swig bison libtool autoconf libglib2.0-dev libicu-dev libfann-dev realpath
- sudo apt-get install -y gcc-4.8 g++-4.8 - sudo apt-get install -y gcc-4.8 g++-4.8
- export CC="gcc-4.8" - export CC="gcc-4.8"
- export TMPDIR="/tmp/${TRAVIS_PYTHON_VERSION}"
python: python:
- "3.4" - "3.4"
- "3.5" - "3.5"
@ -15,6 +16,9 @@ python:
cache: pocketsphinx-python cache: pocketsphinx-python
# command to install dependencies # command to install dependencies
install: install:
- rm -rf ${TMPDIR}
- mkdir ${TMPDIR}
- echo ${TMPDIR}
- VIRTUALENV_ROOT=${VIRTUAL_ENV} ./dev_setup.sh - VIRTUALENV_ROOT=${VIRTUAL_ENV} ./dev_setup.sh
- pip install -r requirements.txt - pip install -r requirements.txt
- pip install -r test-requirements.txt - pip install -r test-requirements.txt

View File

@ -371,6 +371,11 @@ class DeviceApi(Api):
skills = {s['name']: s for s in data['skills']} skills = {s['name']: s for s in data['skills']}
to_send['skills'] = [skills[key] for key in skills] to_send['skills'] = [skills[key] for key in skills]
# Finalize skill_gid with uuid if needed
for s in to_send['skills']:
s['skill_gid'] = s.get('skill_gid', '').replace(
'@|', '@{}|'.format(self.identity.uuid))
self.request({ self.request({
"method": "PUT", "method": "PUT",
"path": "/" + self.identity.uuid + "/skillJson", "path": "/" + self.identity.uuid + "/skillJson",

View File

@ -0,0 +1,43 @@
# Copyright 2019 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.
#
import os
from os.path import join, expanduser, exists
from msm import MycroftSkillsManager, SkillRepo
from mycroft.util.combo_lock import ComboLock
mycroft_msm_lock = ComboLock('/tmp/mycroft-msm.lck')
def create_msm(config):
""" Create msm object from config. """
msm_config = config['skills']['msm']
repo_config = msm_config['repo']
data_dir = expanduser(config['data_dir'])
skills_dir = join(data_dir, msm_config['directory'])
repo_cache = join(data_dir, repo_config['cache'])
platform = config['enclosure'].get('platform', 'default')
with mycroft_msm_lock:
# Try to create the skills directory if it doesn't exist
if not exists(skills_dir):
os.makedirs(skills_dir)
return MycroftSkillsManager(
platform=platform, skills_dir=skills_dir,
repo=SkillRepo(repo_cache, repo_config['url'],
repo_config['branch']),
versioned=msm_config['versioned'])

View File

@ -63,14 +63,52 @@ import json
import hashlib import hashlib
import os import os
import yaml import yaml
import time
import copy
import re
from threading import Timer from threading import Timer
from os.path import isfile, join, expanduser from os.path import isfile, join, expanduser
from requests.exceptions import RequestException, HTTPError from requests.exceptions import RequestException, HTTPError
from msm import SkillEntry
from mycroft.api import DeviceApi, is_paired from mycroft.api import DeviceApi, is_paired
from mycroft.util.log import LOG from mycroft.util.log import LOG
from mycroft.util import camel_case_split
from mycroft.configuration import ConfigurationManager from mycroft.configuration import ConfigurationManager
from .msm_wrapper import create_msm
msm = None
msm_creation_time = 0
def build_global_id(directory, config):
""" Create global id for the skill.
TODO: Handle dirty skill
Arguments:
directory: skill directory
config: config for the device to fetch msm setup
"""
# Update the msm object if it's more than an hour old
global msm
global msm_creation_time
if msm is None or time.time() - msm_creation_time > 60 * 60:
msm_creation_time = time.time()
msm = create_msm(config)
s = SkillEntry.from_folder(directory, msm)
# If modified prepend the device uuid
return s.skill_gid, s.meta_info.get('display_name')
def display_name(name):
""" Splits camelcase and removes leading/trailing Skill. """
name = re.sub(r'(^[Ss]kill|[Ss]kill$)', '', name)
return camel_case_split(name)
class DelayRequest(Exception): class DelayRequest(Exception):
""" Indicate that the next request should be delayed. """ """ Indicate that the next request should be delayed. """
@ -82,8 +120,10 @@ class SkillSettings(dict):
also syncs to the backend for skill settings also syncs to the backend for skill settings
Args: Args:
directory (str): Path to storage directory directory (str): Path to storage directory
name (str): user readable name associated with the settings name (str): user readable name associated with the settings
no_upload (bool): True if the upload to mycroft servers should be
disabled.
""" """
def __init__(self, directory, name): def __init__(self, directory, name):
@ -100,6 +140,8 @@ class SkillSettings(dict):
# set file paths # set file paths
self._settings_path = join(directory, 'settings.json') self._settings_path = join(directory, 'settings.json')
self._meta_path = _get_meta_path(directory) self._meta_path = _get_meta_path(directory)
self._directory = directory
self.is_alive = True self.is_alive = True
self.loaded_hash = hash(json.dumps(self, sort_keys=True)) self.loaded_hash = hash(json.dumps(self, sort_keys=True))
self._complete_intialization = False self._complete_intialization = False
@ -108,11 +150,33 @@ class SkillSettings(dict):
self._user_identity = None self._user_identity = None
self.changed_callback = None self.changed_callback = None
self._poll_timer = None self._poll_timer = None
self._blank_poll_timer = None
self._is_alive = True self._is_alive = True
self._meta_upload = True # Flag allowing upload of settings meta
# Add Information extracted from the skills-meta.json entry for the
# skill.
skill_gid, disp_name = build_global_id(self._directory, self.config)
self.__skill_gid = skill_gid
self.display_name = disp_name
# if settingsmeta exist # if settingsmeta exist
if self._meta_path: if self._meta_path:
self._poll_skill_settings() self._poll_skill_settings()
# if not disallowed by user upload an entry for all skills installed
elif self.config['skills']['upload_skill_manifest']:
self._blank_poll_timer = Timer(1, self._init_blank_meta)
self._blank_poll_timer.daemon = True
self._blank_poll_timer.start()
@property
def skill_gid(self):
""" Finalizes the skill gid to include device uuid if needed. """
if is_paired():
return self.__skill_gid.replace('@|', '@{}|'.format(
DeviceApi().identity.uuid))
else:
return self.__skill_gid
def __hash__(self): def __hash__(self):
""" Simple object unique hash. """ """ Simple object unique hash. """
@ -128,6 +192,8 @@ class SkillSettings(dict):
self._is_alive = False self._is_alive = False
if self._poll_timer: if self._poll_timer:
self._poll_timer.cancel() self._poll_timer.cancel()
if self._blank_poll_timer:
self._blank_poll_timer.cancel()
def set_changed_callback(self, callback): def set_changed_callback(self, callback):
""" Set callback to perform when server settings have changed. """ Set callback to perform when server settings have changed.
@ -158,34 +224,17 @@ class SkillSettings(dict):
except RequestException: except RequestException:
return return
hashed_meta = self._get_meta_hash(settings_meta) settings = (self._request_my_settings(self.skill_gid) or
skill_settings = self._request_other_settings(hashed_meta) self._request_other_settings(self.skill_gid))
# if hash is new then there is a diff version of settingsmeta if settings:
if self._is_new_hash(hashed_meta): self.save_skill_settings(settings)
# first look at all other devices on user account to see
# if the settings exist. if it does then sync with device # TODO if this skill_gid is not a modified version check if a modified
if skill_settings: # version exists on the server and delete it
# not_owner flags that this settings is loaded from
# another device. If a skill settings doesn't have # Always try to upload settingsmeta on startup
# not_owner, then the skill is created from that device self._upload_meta(settings_meta, self.skill_gid)
self['not_owner'] = True
self.save_skill_settings(skill_settings)
else: # upload skill settings if
uuid = self._load_uuid()
if uuid is not None:
self._delete_metadata(uuid)
self._upload_meta(settings_meta, hashed_meta)
else: # hash is not new
if skill_settings is not None:
self['not_owner'] = True
self.save_skill_settings(skill_settings)
else:
settings = self._request_my_settings(hashed_meta)
if settings is None:
# metadata got deleted from Home, send up
self._upload_meta(settings_meta, hashed_meta)
else:
self.save_skill_settings(settings)
self._complete_intialization = True self._complete_intialization = True
@property @property
@ -202,24 +251,41 @@ class SkillSettings(dict):
return super(SkillSettings, self).__setitem__(key, value) return super(SkillSettings, self).__setitem__(key, value)
def _load_settings_meta(self): def _load_settings_meta(self):
""" Loads settings metadata from skills path. """ """ Load settings metadata from the skill folder.
if not self._meta_path:
return None
_, ext = os.path.splitext(self._meta_path) If no settingsmeta exists a basic settingsmeta will be created
json_file = True if ext.lower() == ".json" else False containing a basic identifier.
try: Returns:
with open(self._meta_path, encoding='utf-8') as f: (dict) settings meta
if json_file: """
data = json.load(f) if self._meta_path and os.path.isfile(self._meta_path):
else: _, ext = os.path.splitext(self._meta_path)
data = yaml.load(f) json_file = True if ext.lower() == ".json" else False
return data
except Exception as e: try:
LOG.error("Failed to load setting file: " + self._meta_path) with open(self._meta_path, encoding='utf-8') as f:
LOG.error(repr(e)) if json_file:
return None data = json.load(f)
else:
data = yaml.load(f)
except Exception as e:
LOG.error("Failed to load setting file: " + self._meta_path)
LOG.error(repr(e))
data = copy.copy(BLANK_META)
else:
data = {}
# Insert skill_gid and display_name
data['skill_gid'] = self.skill_gid
data['display_name'] = (self.display_name or data.get('name') or
display_name(self.name))
# Backwards compatibility:
if 'name' not in data:
data['name'] = data['display_name']
return data
def _send_settings_meta(self, settings_meta): def _send_settings_meta(self, settings_meta):
""" Send settingsmeta to the server. """ Send settingsmeta to the server.
@ -229,18 +295,21 @@ class SkillSettings(dict):
Returns: Returns:
dict: uuid, a unique id for the setting meta data dict: uuid, a unique id for the setting meta data
""" """
try: if self._meta_upload:
uuid = self._put_metadata(settings_meta) try:
return uuid uuid = self._put_metadata(settings_meta)
except HTTPError as e: return uuid
if e.response.status_code in [422, 500, 501]: except HTTPError as e:
raise DelayRequest if e.response.status_code in [422, 500, 501]:
else: self._meta_upload = False
raise DelayRequest
else:
LOG.error(e)
return None
except Exception as e:
LOG.error(e) LOG.error(e)
return None return None
except Exception as e:
LOG.error(e)
return None
def save_skill_settings(self, skill_settings): def save_skill_settings(self, skill_settings):
""" Takes skill object and save onto self """ Takes skill object and save onto self
@ -248,61 +317,22 @@ class SkillSettings(dict):
Args: Args:
skill_settings (dict): skill skill_settings (dict): skill
""" """
if self._is_new_hash(skill_settings['identifier']): if 'skillMetadata' in skill_settings:
self._save_uuid(skill_settings['uuid']) sections = skill_settings['skillMetadata']['sections']
self._save_hash(skill_settings['identifier']) for section in sections:
sections = skill_settings['skillMetadata']['sections'] for field in section["fields"]:
for section in sections: if "name" in field and "value" in field:
for field in section["fields"]: # Bypass the change lock to allow server to update
if "name" in field and "value" in field: # during skill init
self[field['name']] = field['value'] super(SkillSettings, self).__setitem__(field['name'],
self.store() field['value'])
self.store()
def _load_uuid(self):
""" Loads uuid
Returns:
str: uuid of the previous settingsmeta
"""
directory = self.config.get("skills")["directory"]
directory = join(directory, self.name)
directory = expanduser(directory)
uuid_file = join(directory, 'uuid')
uuid = None
if isfile(uuid_file):
with open(uuid_file, 'r') as f:
uuid = f.read()
return uuid
def _save_uuid(self, uuid):
""" Saves uuid.
Args:
uuid (str): uuid, unique id of new settingsmeta
"""
directory = self.config.get("skills")["directory"]
directory = join(directory, self.name)
directory = expanduser(directory)
uuid_file = join(directory, 'uuid')
os.makedirs(directory, exist_ok=True)
with open(uuid_file, 'w') as f:
f.write(str(uuid))
def _uuid_exist(self):
""" Checks if there is an uuid file.
Returns:
bool: True if uuid file exist False otherwise
"""
directory = self.config.get("skills")["directory"]
directory = join(directory, self.name)
directory = expanduser(directory)
uuid_file = join(directory, 'uuid')
return isfile(uuid_file)
def _migrate_settings(self, settings_meta): def _migrate_settings(self, settings_meta):
""" sync settings.json and settingsmeta in memory """ """ sync settings.json and settingsmeta in memory """
meta = settings_meta.copy() meta = settings_meta.copy()
if 'skillMetadata' not in meta:
return meta
self.load_skill_settings_from_file() self.load_skill_settings_from_file()
sections = meta['skillMetadata']['sections'] sections = meta['skillMetadata']['sections']
for i, section in enumerate(sections): for i, section in enumerate(sections):
@ -314,90 +344,50 @@ class SkillSettings(dict):
meta['skillMetadata']['sections'] = sections meta['skillMetadata']['sections'] = sections
return meta return meta
def _upload_meta(self, settings_meta, hashed_meta): def _upload_meta(self, settings_meta, identifier):
""" uploads the new meta data to settings with settings migration """ uploads the new meta data to settings with settings migration
Args: Args:
settings_meta (dict): from settingsmeta.json or settingsmeta.yaml settings_meta (dict): settingsmeta.json or settingsmeta.yaml
hashed_meta (str): {skill-folder}-settinsmeta.json identifier (str): identifier for skills meta data
""" """
LOG.debug('Uploading settings meta for {}'.format(identifier))
meta = self._migrate_settings(settings_meta) meta = self._migrate_settings(settings_meta)
meta['identifier'] = str(hashed_meta) meta['identifier'] = identifier
response = self._send_settings_meta(meta) response = self._send_settings_meta(meta)
if response and 'uuid' in response:
self._save_uuid(response['uuid'])
if 'not_owner' in self:
del self['not_owner']
self._save_hash(hashed_meta)
def hash(self, string): def hash(self, string):
""" md5 hasher for consistency across cpu architectures """ """ md5 hasher for consistency across cpu architectures """
return hashlib.md5(bytes(string, 'utf-8')).hexdigest() return hashlib.md5(bytes(string, 'utf-8')).hexdigest()
def _get_meta_hash(self, settings_meta):
""" Gets the hash of skill
Args:
settings_meta (dict): settingsmeta object
Returns:
_hash (str): hashed to identify skills
"""
_hash = self.hash(json.dumps(settings_meta, sort_keys=True) +
self._user_identity)
return "{}--{}".format(self.name, _hash)
def _save_hash(self, hashed_meta):
""" Saves hashed_meta to settings directory.
Args:
hashed_meta (str): hash of new settingsmeta
"""
directory = self.config.get("skills")["directory"]
directory = join(directory, self.name)
directory = expanduser(directory)
hash_file = join(directory, 'hash')
os.makedirs(directory, exist_ok=True)
with open(hash_file, 'w') as f:
f.write(hashed_meta)
def _is_new_hash(self, hashed_meta):
""" Check if stored hash is the same as current.
If the hashed file does not exist, usually in the
case of first load, then the create it and return True
Args:
hashed_meta (str): hash of metadata and uuid of device
Returns:
bool: True if hash is new, otherwise False
"""
directory = self.config.get("skills")["directory"]
directory = join(directory, self.name)
directory = expanduser(directory)
hash_file = join(directory, 'hash')
if isfile(hash_file):
with open(hash_file, 'r') as f:
current_hash = f.read()
return False if current_hash == str(hashed_meta) else True
return True
def update_remote(self): def update_remote(self):
""" update settings state from server """ """ update settings state from server """
skills_settings = None
settings_meta = self._load_settings_meta() settings_meta = self._load_settings_meta()
if settings_meta is None: if settings_meta is None:
return return
hashed_meta = self._get_meta_hash(settings_meta) # Get settings
if self.get('not_owner'): skills_settings = (self._request_my_settings(self.skill_gid) or
skills_settings = self._request_other_settings(hashed_meta) self._request_other_settings(self.skill_gid))
if not skills_settings:
skills_settings = self._request_my_settings(hashed_meta)
if skills_settings is not None: if skills_settings is not None:
self.save_skill_settings(skills_settings) self.save_skill_settings(skills_settings)
self.store()
else: else:
LOG.debug("No Settings on server for {}".format(self.skill_gid))
# Settings meta doesn't exist on server push them
settings_meta = self._load_settings_meta() settings_meta = self._load_settings_meta()
self._upload_meta(settings_meta, hashed_meta) self._upload_meta(settings_meta, self.skill_gid)
def _init_blank_meta(self):
""" Send blank settingsmeta to remote. """
try:
if not is_paired() and self.is_alive:
self._blank_poll_timer = Timer(60, self._init_blank_meta)
self._blank_poll_timer.daemon = True
self._blank_poll_timer.start()
else:
self.initialize_remote_settings()
except Exception as e:
LOG.exception('Failed to send blank meta: {}'.format(repr(e)))
def _poll_skill_settings(self): def _poll_skill_settings(self):
""" If identifier exists for this skill poll to backend to """ If identifier exists for this skill poll to backend to
@ -466,6 +456,9 @@ class SkillSettings(dict):
dict: skills object dict: skills object
""" """
meta = settings_meta.copy() meta = settings_meta.copy()
if 'skillMetadata' not in settings_meta:
return meta
sections = meta['skillMetadata']['sections'] sections = meta['skillMetadata']['sections']
for i, section in enumerate(sections): for i, section in enumerate(sections):
@ -504,7 +497,6 @@ class SkillSettings(dict):
def _request_my_settings(self, identifier): def _request_my_settings(self, identifier):
""" Get skill settings for this device associated """ Get skill settings for this device associated
with the identifier with the identifier
Args: Args:
identifier (str): a hashed_meta identifier (str): a hashed_meta
Returns: Returns:
@ -515,12 +507,33 @@ class SkillSettings(dict):
# this loads the settings into memory for use in self.store # this loads the settings into memory for use in self.store
for skill_settings in settings: for skill_settings in settings:
if skill_settings['identifier'] == identifier: if skill_settings['identifier'] == identifier:
LOG.debug("Fetched settings for {}".format(identifier))
skill_settings = \ skill_settings = \
self._type_cast(skill_settings, to_platform='core') self._type_cast(skill_settings, to_platform='core')
self._remote_settings = skill_settings self._remote_settings = skill_settings
return skill_settings return skill_settings
return None return None
def _request_other_settings(self, identifier):
""" Retrieve skill settings from other devices by identifier
Args:
identifier (str): identifier for this skill
Returns:
settings (dict or None): the retrieved settings or None
"""
path = \
"/" + self._device_identity + "/userSkill?identifier=" + identifier
try:
user_skill = self.api.request({"method": "GET", "path": path})
except RequestException:
# Some kind of Timeout, connection HTTPError, etc.
user_skill = None
if not user_skill or not user_skill[0]:
return None
else:
settings = self._type_cast(user_skill[0], to_platform='core')
return settings
def _request_settings(self): def _request_settings(self):
""" Get all skill settings for this device from server. """ Get all skill settings for this device from server.
@ -538,27 +551,6 @@ class SkillSettings(dict):
settings = [skills for skills in settings if skills is not None] settings = [skills for skills in settings if skills is not None]
return settings return settings
def _request_other_settings(self, identifier):
""" Retrieve skill settings from other devices by identifier
Args:
identifier (str): identifier for this skill
Returns:
settings (dict or None): the retrieved settings or None
"""
path = \
"/" + self._device_identity + "/userSkill?identifier=" + identifier
try:
user_skill = self.api.request({"method": "GET", "path": path})
except RequestException:
# Some kind of Timeout, connection HTTPError, etc.
user_skill = None
if not user_skill:
return None
else:
settings = self._type_cast(user_skill[0], to_platform='core')
return settings
def _put_metadata(self, settings_meta): def _put_metadata(self, settings_meta):
""" PUT settingsmeta to backend to be configured in server. """ PUT settingsmeta to backend to be configured in server.
used in place of POST and PATCH. used in place of POST and PATCH.
@ -576,6 +568,7 @@ class SkillSettings(dict):
def _delete_metadata(self, uuid): def _delete_metadata(self, uuid):
""" Delete the current skill metadata """ Delete the current skill metadata
TODO: UPDATE FOR NEW BACKEND
Args: Args:
uuid (str): unique id of the skill uuid (str): unique id of the skill
""" """
@ -594,7 +587,8 @@ class SkillSettings(dict):
@property @property
def _should_upload_from_change(self): def _should_upload_from_change(self):
changed = False changed = False
if hasattr(self, '_remote_settings'): if (hasattr(self, '_remote_settings') and
'skillMetadata' in self._remote_settings):
sections = self._remote_settings['skillMetadata']['sections'] sections = self._remote_settings['skillMetadata']['sections']
for i, section in enumerate(sections): for i, section in enumerate(sections):
for j, field in enumerate(section['fields']): for j, field in enumerate(section['fields']):
@ -624,11 +618,7 @@ class SkillSettings(dict):
if self._should_upload_from_change: if self._should_upload_from_change:
settings_meta = self._load_settings_meta() settings_meta = self._load_settings_meta()
hashed_meta = self._get_meta_hash(settings_meta) self._upload_meta(settings_meta, self.skill_gid)
uuid = self._load_uuid()
if uuid is not None:
self._delete_metadata(uuid)
self._upload_meta(settings_meta, hashed_meta)
def _get_meta_path(base_directory): def _get_meta_path(base_directory):

View File

@ -33,7 +33,7 @@ from mycroft.util.log import LOG
from mycroft.api import DeviceApi, is_paired from mycroft.api import DeviceApi, is_paired
from .core import load_skill, create_skill_descriptor, MainModule from .core import load_skill, create_skill_descriptor, MainModule
from .msm_wrapper import create_msm as msm_creator
DEBUG = Configuration.get().get("debug", False) DEBUG = Configuration.get().get("debug", False)
skills_config = Configuration.get().get("skills") skills_config = Configuration.get().get("skills")
@ -132,23 +132,7 @@ class SkillManager(Thread):
@staticmethod @staticmethod
def create_msm(): def create_msm():
config = Configuration.get() return msm_creator(Configuration.get())
msm_config = config['skills']['msm']
repo_config = msm_config['repo']
data_dir = expanduser(config['data_dir'])
skills_dir = join(data_dir, msm_config['directory'])
# Try to create the skills directory if it doesn't exist
if not exists(skills_dir):
os.makedirs(skills_dir)
repo_cache = join(data_dir, repo_config['cache'])
platform = config['enclosure'].get('platform', 'default')
return MycroftSkillsManager(
platform=platform, skills_dir=skills_dir,
repo=SkillRepo(
repo_cache, repo_config['url'], repo_config['branch']
), versioned=msm_config['versioned']
)
def schedule_now(self, message=None): def schedule_now(self, message=None):
self.next_download = time.time() - 1 self.next_download = time.time() - 1

View File

@ -23,7 +23,7 @@ google-api-python-client==1.6.4
fasteners==0.14.1 fasteners==0.14.1
PyYAML==3.13 PyYAML==3.13
msm==0.7.3 msm==0.7.5
msk==0.3.12 msk==0.3.12
adapt-parser==0.3.2 adapt-parser==0.3.2
padatious==0.4.6 padatious==0.4.6

View File

@ -14,6 +14,7 @@
# #
import json import json
import unittest import unittest
from mock import MagicMock
from os import remove from os import remove
from os.path import join, dirname from os.path import join, dirname
@ -21,6 +22,10 @@ from os.path import join, dirname
from mycroft.skills.settings import SkillSettings from mycroft.skills.settings import SkillSettings
SkillSettings._poll_skill_settings = MagicMock()
SkillSettings._init_blank_meta = MagicMock()
class SkillSettingsTest(unittest.TestCase): class SkillSettingsTest(unittest.TestCase):
def setUp(self): def setUp(self):
try: try:

View File

@ -1,5 +1,6 @@
import unittest import unittest
import mock import mock
import copy
import tempfile import tempfile
from os.path import exists, join from os.path import exists, join
from shutil import rmtree from shutil import rmtree
@ -9,7 +10,6 @@ from mycroft.configuration import Configuration
from mycroft.skills.skill_manager import SkillManager from mycroft.skills.skill_manager import SkillManager
BASE_CONF = base_config() BASE_CONF = base_config()
BASE_CONF['data_dir'] = tempfile.mkdtemp()
BASE_CONF['skills'] = { BASE_CONF['skills'] = {
'msm': { 'msm': {
'directory': 'skills', 'directory': 'skills',
@ -17,7 +17,7 @@ BASE_CONF['skills'] = {
'repo': { 'repo': {
'cache': '.skills-repo', 'cache': '.skills-repo',
'url': 'https://github.com/MycroftAI/mycroft-skills', 'url': 'https://github.com/MycroftAI/mycroft-skills',
'branch': '18.08' 'branch': '19.02'
} }
}, },
'update_interval': 3600, 'update_interval': 3600,
@ -56,14 +56,16 @@ class MycroftSkillTest(unittest.TestCase):
self.emitter.reset() self.emitter.reset()
self.temp_dir = tempfile.mkdtemp() self.temp_dir = tempfile.mkdtemp()
@mock.patch.dict(Configuration._Configuration__config, BASE_CONF)
def test_create_manager(self): def test_create_manager(self):
""" Verify that the skill manager and msm loads as expected and """ Verify that the skill manager and msm loads as expected and
that the skills dir is created as needed. that the skills dir is created as needed.
""" """
SkillManager(self.emitter) conf = copy.deepcopy(BASE_CONF)
self.assertTrue(exists(join(BASE_CONF['data_dir'], 'skills'))) conf['data_dir'] = self.temp_dir
with mock.patch.dict(Configuration._Configuration__config,
BASE_CONF):
SkillManager(self.emitter)
self.assertTrue(exists(join(BASE_CONF['data_dir'], 'skills')))
@classmethod def tearDown(self):
def tearDownClass(cls): rmtree(self.temp_dir)
rmtree(BASE_CONF['data_dir'])

View File

@ -15,23 +15,25 @@
import unittest import unittest
from shutil import rmtree from shutil import rmtree
from os.path import exists, isfile from os.path import exists, isfile, join
from tempfile import gettempdir
from mycroft.util import create_signal, check_for_signal from mycroft.util import create_signal, check_for_signal
class TestSignals(unittest.TestCase): class TestSignals(unittest.TestCase):
def setUp(self): def setUp(self):
if exists('/tmp/mycroft'): if exists(join(gettempdir(), 'mycroft')):
rmtree('/tmp/mycroft') rmtree(join(gettempdir(), 'mycroft'))
def test_create_signal(self): def test_create_signal(self):
create_signal('test_signal') create_signal('test_signal')
self.assertTrue(isfile('/tmp/mycroft/ipc/signal/test_signal')) self.assertTrue(isfile(join(gettempdir(),
'mycroft/ipc/signal/test_signal')))
def test_check_signal(self): def test_check_signal(self):
if exists('/tmp/mycroft'): if exists(join(gettempdir(), 'mycroft')):
rmtree('/tmp/mycroft') rmtree(join(gettempdir(), 'mycroft'))
# check that signal is not found if file does not exist # check that signal is not found if file does not exist
self.assertFalse(check_for_signal('test_signal')) self.assertFalse(check_for_signal('test_signal'))
@ -39,7 +41,8 @@ class TestSignals(unittest.TestCase):
create_signal('test_signal') create_signal('test_signal')
self.assertTrue(check_for_signal('test_signal')) self.assertTrue(check_for_signal('test_signal'))
# Check that the signal is removed after use # Check that the signal is removed after use
self.assertFalse(isfile('/tmp/mycroft/ipc/signal/test_signal')) self.assertFalse(isfile(join(gettempdir(),
'mycroft/ipc/signal/test_signal')))
if __name__ == "__main__": if __name__ == "__main__":