feat: add "progress" page for executing upgrades
show scrolling stdout from subprocess nb. this does *not* show stderr, although that is captured
This commit is contained in:
parent
e5e31a7d32
commit
8669ca2283
6 changed files with 392 additions and 41 deletions
|
@ -1342,8 +1342,11 @@ class TestMasterView(WebTestCase):
|
|||
|
||||
def test_execute(self):
|
||||
self.pyramid_config.add_route('settings.view', '/settings/{name}')
|
||||
self.pyramid_config.add_route('progress', '/progress/{key}')
|
||||
model = self.app.model
|
||||
self.app.save_setting(self.session, 'foo', 'bar')
|
||||
user = model.User(username='barney')
|
||||
self.session.add(user)
|
||||
self.session.commit()
|
||||
|
||||
with patch.multiple(mod.MasterView, create=True,
|
||||
|
@ -1352,18 +1355,54 @@ class TestMasterView(WebTestCase):
|
|||
Session=MagicMock(return_value=self.session)):
|
||||
view = self.make_view()
|
||||
self.request.matchdict = {'name': 'foo'}
|
||||
self.request.session.id = 'mockid'
|
||||
self.request.user = user
|
||||
|
||||
# basic usage, redirects to view obj url
|
||||
response = view.execute()
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(self.request.session.pop_flash(), ["Setting was executed."])
|
||||
|
||||
# execution error
|
||||
with patch.object(view, 'execute_instance', side_effect=RuntimeError):
|
||||
# basic usage; user is shown progress page
|
||||
with patch.object(mod, 'threading') as threading:
|
||||
response = view.execute()
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(self.request.session.pop_flash(), [])
|
||||
self.assertEqual(self.request.session.pop_flash('error'), ["RuntimeError"])
|
||||
threading.Thread.return_value.start.assert_called_once_with()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_execute_thread(self):
|
||||
model = self.app.model
|
||||
enum = self.app.enum
|
||||
user = model.User(username='barney')
|
||||
self.session.add(user)
|
||||
upgrade = model.Upgrade(description='test', created_by=user,
|
||||
status=enum.UpgradeStatus.PENDING)
|
||||
self.session.add(upgrade)
|
||||
self.session.commit()
|
||||
|
||||
with patch.multiple(mod.MasterView, create=True,
|
||||
model_class=model.Upgrade):
|
||||
view = self.make_view()
|
||||
|
||||
# basic execute, no progress
|
||||
with patch.object(view, 'execute_instance') as execute_instance:
|
||||
view.execute_thread({'uuid': upgrade.uuid}, user.uuid)
|
||||
execute_instance.assert_called_once()
|
||||
|
||||
# basic execute, with progress
|
||||
with patch.object(view, 'execute_instance') as execute_instance:
|
||||
progress = MagicMock()
|
||||
view.execute_thread({'uuid': upgrade.uuid}, user.uuid, progress=progress)
|
||||
execute_instance.assert_called_once()
|
||||
progress.handle_success.assert_called_once_with()
|
||||
|
||||
# error, no progress
|
||||
with patch.object(view, 'execute_instance') as execute_instance:
|
||||
execute_instance.side_effect = RuntimeError
|
||||
view.execute_thread({'uuid': upgrade.uuid}, user.uuid)
|
||||
execute_instance.assert_called_once()
|
||||
|
||||
# error, with progress
|
||||
with patch.object(view, 'execute_instance') as execute_instance:
|
||||
progress = MagicMock()
|
||||
execute_instance.side_effect = RuntimeError
|
||||
view.execute_thread({'uuid': upgrade.uuid}, user.uuid, progress=progress)
|
||||
execute_instance.assert_called_once()
|
||||
progress.handle_error.assert_called_once()
|
||||
|
||||
def test_configure(self):
|
||||
self.pyramid_config.include('wuttaweb.views.common')
|
||||
|
|
|
@ -7,6 +7,7 @@ from unittest.mock import patch, MagicMock
|
|||
|
||||
from wuttaweb.views import upgrades as mod
|
||||
from wuttjamaican.exc import ConfigurationError
|
||||
from wuttaweb.progress import get_progress_session
|
||||
from tests.util import WebTestCase
|
||||
|
||||
|
||||
|
@ -220,7 +221,7 @@ class TestUpgradeView(WebTestCase):
|
|||
python = sys.executable
|
||||
|
||||
# script not yet confiugred
|
||||
self.assertRaises(ConfigurationError, view.execute_instance, upgrade)
|
||||
self.assertRaises(ConfigurationError, view.execute_instance, upgrade, user)
|
||||
|
||||
# script w/ success
|
||||
goodpy = self.write_file('good.py', """
|
||||
|
@ -234,7 +235,7 @@ sys.exit(0)
|
|||
self.assertEqual(upgrade.status, enum.UpgradeStatus.PENDING)
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
with patch.object(self.config, 'usedb', new=True):
|
||||
view.execute_instance(upgrade)
|
||||
view.execute_instance(upgrade, user)
|
||||
self.assertIsNotNone(upgrade.executed)
|
||||
self.assertIs(upgrade.executed_by, user)
|
||||
self.assertEqual(upgrade.status, enum.UpgradeStatus.SUCCESS)
|
||||
|
@ -261,7 +262,7 @@ sys.exit(42)
|
|||
self.assertEqual(upgrade.status, enum.UpgradeStatus.PENDING)
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
with patch.object(self.config, 'usedb', new=True):
|
||||
view.execute_instance(upgrade)
|
||||
view.execute_instance(upgrade, user)
|
||||
self.assertIsNotNone(upgrade.executed)
|
||||
self.assertIs(upgrade.executed_by, user)
|
||||
self.assertEqual(upgrade.status, enum.UpgradeStatus.FAILURE)
|
||||
|
@ -270,6 +271,93 @@ sys.exit(42)
|
|||
with open(view.get_upgrade_filepath(upgrade, 'stderr.log')) as f:
|
||||
self.assertEqual(f.read(), 'hello from bad.py')
|
||||
|
||||
def test_execute_progress(self):
|
||||
model = self.app.model
|
||||
enum = self.app.enum
|
||||
view = self.make_view()
|
||||
|
||||
user = model.User(username='barney')
|
||||
self.session.add(user)
|
||||
upgrade = model.Upgrade(description='test', created_by=user,
|
||||
status=enum.UpgradeStatus.PENDING)
|
||||
self.session.add(upgrade)
|
||||
self.session.commit()
|
||||
|
||||
stdout = self.write_file('stdout.log', 'hello 001\n')
|
||||
|
||||
self.request.matchdict = {'uuid': upgrade.uuid}
|
||||
with patch.multiple(mod.UpgradeView,
|
||||
Session=MagicMock(return_value=self.session),
|
||||
get_upgrade_filepath=MagicMock(return_value=stdout)):
|
||||
|
||||
# nb. this is used to identify progress tracker
|
||||
self.request.session.id = 'mockid#1'
|
||||
|
||||
# first call should get the full contents
|
||||
context = view.execute_progress()
|
||||
self.assertFalse(context.get('complete'))
|
||||
self.assertFalse(context.get('error'))
|
||||
# nb. newline is converted to <br>
|
||||
self.assertEqual(context['stdout'], 'hello 001<br />')
|
||||
|
||||
# next call should get any new contents
|
||||
with open(stdout, 'a') as f:
|
||||
f.write('hello 002\n')
|
||||
context = view.execute_progress()
|
||||
self.assertFalse(context.get('complete'))
|
||||
self.assertFalse(context.get('error'))
|
||||
self.assertEqual(context['stdout'], 'hello 002<br />')
|
||||
|
||||
# nb. switch to a different progress tracker
|
||||
self.request.session.id = 'mockid#2'
|
||||
|
||||
# first call should get the full contents
|
||||
context = view.execute_progress()
|
||||
self.assertFalse(context.get('complete'))
|
||||
self.assertFalse(context.get('error'))
|
||||
self.assertEqual(context['stdout'], 'hello 001<br />hello 002<br />')
|
||||
|
||||
# mark progress complete
|
||||
session = get_progress_session(self.request, 'upgrades.execute')
|
||||
session.load()
|
||||
session['complete'] = True
|
||||
session['success_msg'] = 'yay!'
|
||||
session.save()
|
||||
|
||||
# next call should reflect that
|
||||
self.assertEqual(self.request.session.pop_flash(), [])
|
||||
context = view.execute_progress()
|
||||
self.assertTrue(context.get('complete'))
|
||||
self.assertFalse(context.get('error'))
|
||||
# nb. this is missing b/c we already got all contents
|
||||
self.assertNotIn('stdout', context)
|
||||
self.assertEqual(self.request.session.pop_flash(), ['yay!'])
|
||||
|
||||
# nb. switch to a different progress tracker
|
||||
self.request.session.id = 'mockid#3'
|
||||
|
||||
# first call should get the full contents
|
||||
context = view.execute_progress()
|
||||
self.assertFalse(context.get('complete'))
|
||||
self.assertFalse(context.get('error'))
|
||||
self.assertEqual(context['stdout'], 'hello 001<br />hello 002<br />')
|
||||
|
||||
# mark progress error
|
||||
session = get_progress_session(self.request, 'upgrades.execute')
|
||||
session.load()
|
||||
session['error'] = True
|
||||
session['error_msg'] = 'omg!'
|
||||
session.save()
|
||||
|
||||
# next call should reflect that
|
||||
self.assertEqual(self.request.session.pop_flash('error'), [])
|
||||
context = view.execute_progress()
|
||||
self.assertFalse(context.get('complete'))
|
||||
self.assertTrue(context.get('error'))
|
||||
# nb. this is missing b/c we already got all contents
|
||||
self.assertNotIn('stdout', context)
|
||||
self.assertEqual(self.request.session.pop_flash('error'), ['omg!'])
|
||||
|
||||
def test_configure_get_simple_settings(self):
|
||||
# sanity/coverage check
|
||||
view = self.make_view()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue