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

[FIX] Permission error when saving settings #2147

Merged
merged 5 commits into from
Apr 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions Orange/widgets/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import copy
import itertools
import os
import logging
import pickle
import time
import warnings
Expand All @@ -40,6 +41,8 @@
from Orange.misc.environ import widget_settings_dir
from Orange.widgets.utils import vartype

log = logging.getLogger(__name__)

__all__ = ["Setting", "SettingsHandler",
"ContextSetting", "ContextHandler",
"DomainContextHandler", "PerfectDomainContextHandler",
Expand Down Expand Up @@ -397,15 +400,20 @@ def write_defaults(self):
should overload the latter."""
filename = self._get_settings_filename()
os.makedirs(os.path.dirname(filename), exist_ok=True)

settings_file = open(filename, "wb")
try:
self.write_defaults_file(settings_file)
except (EOFError, IOError, pickle.PicklingError):
settings_file.close()
os.remove(filename)
else:
settings_file.close()
settings_file = open(filename, "wb")
try:
self.write_defaults_file(settings_file)
except (EOFError, IOError, pickle.PicklingError) as ex:
log.error("Could not write default settings for %s (%s).",
self.widget_class, type(ex).__name__)
settings_file.close()
os.remove(filename)
else:
settings_file.close()
except PermissionError as ex:
log.error("Could not write default settings for %s (%s).",
self.widget_class, type(ex).__name__)

def write_defaults_file(self, settings_file):
"""Write defaults for this widget class to a file
Expand Down
4 changes: 4 additions & 0 deletions Orange/widgets/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def load_tests(loader, tests, pattern):
return unittest.TestSuite([])

widgets_dir = os.path.dirname(Orange.widgets.__file__)
widget_tests_dir = os.path.dirname(__file__)
top_level_dir = os.path.dirname(os.path.dirname(Orange.__file__))

if loader is None:
Expand All @@ -20,6 +21,9 @@ def load_tests(loader, tests, pattern):
load_tests._in_load_tests = True
try:
all_tests = [
# Widgets in this package are discovered separately to avoid
# infinite recursion (see the guard above)
loader.discover(widget_tests_dir, pattern, widget_tests_dir),
loader.discover(widgets_dir, pattern, top_level_dir)
]
finally:
Expand Down
6 changes: 1 addition & 5 deletions Orange/widgets/tests/test_highcharts.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import time
import os
import sys
import unittest

from AnyQt.QtCore import Qt, QPoint, QObject
Expand Down Expand Up @@ -35,9 +33,7 @@ def test_svg_is_svg(self):
self.assertEqual(svg[:5], '<svg ')
self.assertEqual(svg[-6:], '</svg>')

@unittest.skipIf(os.environ.get('APPVEYOR'), 'test stalls on AppVeyor')
@unittest.skipIf(sys.version_info[:2] <= (3, 4),
'the second iteration stalls on Travis / Py3.4')
@unittest.skip("Does not work")
def test_selection(self):

class NoopBridge(QObject):
Expand Down
37 changes: 34 additions & 3 deletions Orange/widgets/tests/test_settings_handler.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# pylint: disable=protected-access
from contextlib import contextmanager
import os
import pickle
from tempfile import mkstemp
from tempfile import mkstemp, NamedTemporaryFile

import unittest
from unittest.mock import patch, Mock
import warnings

from Orange.tests import named_file
from Orange.widgets.settings import SettingsHandler, Setting, SettingProvider,\
VERSION_KEY, rename_setting, Context, migrate_str_to_variable

Expand Down Expand Up @@ -64,6 +68,33 @@ def test_write_defaults(self):

os.remove(settings_file)

def test_write_defaults_handles_permission_error(self):
handler = SettingsHandler()

with named_file("") as f:
handler._get_settings_filename = lambda: f

with patch('Orange.widgets.settings.open', create=True) as mocked_open:
mocked_open.side_effect = PermissionError()

handler.write_defaults()

def test_write_defaults_handles_writing_errors(self):
handler = SettingsHandler()

for error in (EOFError, IOError, pickle.PicklingError):
f = NamedTemporaryFile("wt", delete=False)
f.close() # so it can be opened on windows
handler._get_settings_filename = lambda x=f: x.name

with patch.object(handler, "write_defaults_file") as mocked_write:
mocked_write.side_effect = error()

handler.write_defaults()

# Corrupt setting files should be removed
self.assertFalse(os.path.exists(f.name))

def test_initialize_widget(self):
handler = SettingsHandler()
handler.defaults = {'default': 42, 'setting': 1}
Expand Down Expand Up @@ -286,9 +317,9 @@ def override_default_settings(self, widget, defaults=None):

h = SettingsHandler()
h.widget_class = widget
h.defaults = defaults
filename = h._get_settings_filename()
with open(filename, "wb") as f:
pickle.dump(defaults, f)
h.write_defaults()

yield

Expand Down