Skip to content

Commit a021af4

Browse files
committed
Merge branch 'release/2.32.0'
2 parents e32fbf1 + 11143a2 commit a021af4

File tree

11 files changed

+104
-42
lines changed

11 files changed

+104
-42
lines changed

Makefile

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,8 @@ doc: env
99
env: .env/.up-to-date
1010

1111

12-
.env/.up-to-date: setup.py Makefile test_requirements.txt
12+
.env/.up-to-date: setup.py Makefile setup.cfg
1313
virtualenv --no-site-packages .env
14-
.env/bin/pip install -e .
15-
.env/bin/pip install -r ./*.egg-info/requires.txt || true
16-
.env/bin/pip install -r ./docs/requirements.txt
17-
.env/bin/pip install -r test_requirements.txt
14+
.env/bin/pip install -e .[testing]
1815
touch $@
1916

backslash/client.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ def __init__(self, url, runtoken):
2525
def url(self):
2626
return self._url
2727

28+
def get_ui_url(self, fragment=None):
29+
returned = str(self.url)
30+
if not returned.endswith('/'):
31+
returned += '/'
32+
if not fragment:
33+
fragment = '/'
34+
elif not fragment.startswith('/'):
35+
fragment = '/' + fragment
36+
returned += '#{}'.format(fragment)
37+
return returned
38+
39+
2840
def toggle_user_role(self, user_id, role):
2941
return self.api.call_function('toggle_user_role', {'user_id': user_id, 'role': role})
3042

backslash/contrib/slash_plugin.py

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def new_func(self, *args, **kwargs):
6060

6161
class BackslashPlugin(PluginInterface):
6262

63-
current_test = session = None
63+
client = current_test = session = None
6464

6565
def __init__(self, url=None, keepalive_interval=None, runtoken=None, propagate_exceptions=False):
6666
super(BackslashPlugin, self).__init__()
@@ -75,27 +75,22 @@ def __init__(self, url=None, keepalive_interval=None, runtoken=None, propagate_e
7575

7676
@property
7777
def rest_url(self):
78-
return URL(self._get_backslash_url()).add_path('rest')
78+
if self.client is None:
79+
return None
80+
return self.client.url.add_path('rest')
7981

8082
@property
8183
def webapp_url(self):
82-
return self._url_with_fragment('/')
84+
if self.client is None:
85+
return None
86+
return self.client.get_ui_url()
8387

8488
@property
8589
def session_webapp_url(self):
8690
session = slash.context.session
87-
if session is None:
91+
if session is None or self.client is None:
8892
return None
89-
return self._url_with_fragment('sessions/{}'.format(session.id))
90-
91-
def _url_with_fragment(self, fragment):
92-
returned = str(self._get_backslash_url())
93-
if not returned.endswith('/'):
94-
returned += '/'
95-
if not fragment.startswith('/'):
96-
fragment = '/' + fragment
97-
returned += '#{}'.format(fragment)
98-
return returned
93+
return self.client.get_ui_url('sessions/{}'.format(session.id))
9994

10095
def _handle_exception(self, exc_info):
10196
pass
@@ -199,6 +194,7 @@ def test_avoided(self, reason):
199194
self.test_skip(reason=reason)
200195
self.test_end()
201196

197+
202198
@handle_exceptions
203199
def test_interrupt(self):
204200
if self.current_test is not None:
@@ -411,6 +407,20 @@ def session_end(self):
411407

412408
@handle_exceptions
413409
def error_added(self, result, error):
410+
self._add_exception(result=result, exception=error)
411+
412+
@slash.plugins.register_if(hasattr(slash.hooks, 'interruption_added'))
413+
@handle_exceptions
414+
def interruption_added(self, result, exception):
415+
self._add_exception(result=result, exception=exception, is_interruption=True)
416+
417+
418+
def _add_exception(self, result, exception, is_interruption=False):
419+
has_interruptions = self.client.api.info().endpoints.add_error.version >= 4
420+
if is_interruption and not has_interruptions:
421+
_logger.debug('Server does not support recording is_interruption exceptions. Skipping reporting')
422+
return
423+
414424
if result is slash.session.results.global_result:
415425
error_container = self.session
416426
else:
@@ -420,16 +430,21 @@ def error_added(self, result, error):
420430
_logger.debug('Could not determine error container to report on for {}', result)
421431
return
422432

423-
kwargs = {'exception_type': error.exception_type.__name__ if error.exception_type is not None else None,
424-
'traceback': distill_slash_traceback(error), 'exception_attrs': getattr(error, 'exception_attributes', NOTHING)}
425-
if error.message:
426-
message = error.message
427-
elif hasattr(error, 'exception_str'):
428-
message = error.exception_str
433+
kwargs = {'exception_type': exception.exception_type.__name__ if exception.exception_type is not None else None,
434+
'traceback': distill_slash_traceback(exception), 'exception_attrs': getattr(exception, 'exception_attributes', NOTHING)}
435+
if exception.message:
436+
message = exception.message
437+
elif hasattr(exception, 'exception_str'):
438+
message = exception.exception_str
429439
else:
430-
message = str(error.exception)
440+
message = str(exception.exception)
441+
442+
431443
kwargs['message'] = message
432444

445+
if has_interruptions:
446+
kwargs['is_interruption'] = is_interruption
447+
433448
for compact_variables in [False, True]:
434449
if compact_variables:
435450
for frame in kwargs['traceback']:

backslash/error_container.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313

1414
class ErrorContainer(object):
1515

16-
def add_error(self, message, exception_type=NOTHING, traceback=NOTHING, timestamp=NOTHING, is_failure=NOTHING, exception_attrs=NOTHING):
16+
def add_error(self, message, exception_type=NOTHING, traceback=NOTHING,
17+
timestamp=NOTHING, is_failure=NOTHING, exception_attrs=NOTHING, is_interruption=NOTHING):
1718

1819
kwargs = {self._get_id_key(): self.id, # pylint: disable=no-member
1920
'message': message,
2021
'exception_type': exception_type,
2122
'is_failure': is_failure,
22-
'timestamp': timestamp
23+
'timestamp': timestamp,
24+
'is_interruption': is_interruption,
2325
}
2426

2527
add_error_version = self.client.api.info().endpoints.add_error.version # pylint: disable=no-member

backslash/session.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@
99
from .warning_container import WarningContainer
1010
from .lazy_query import LazyQuery
1111
from .metadata_holder import MetadataHolder
12+
from .timing_container import TimingContainer
1213

1314
APPEND_UPCOMING_TESTS_STR = 'append_upcoming_tests'
1415

15-
class Session(APIObject, MetadataHolder, ErrorContainer, WarningContainer, Archiveable, Commentable, RelatedEntityContainer):
16+
class Session(APIObject, MetadataHolder, ErrorContainer, WarningContainer, Archiveable, Commentable, RelatedEntityContainer, TimingContainer):
1617

1718
@property
1819
def ui_url(self):
19-
return self.client.url + '#/sessions/{}'.format(self.logical_id or self.id)
20+
return self.client.get_ui_url('/sessions/{}'.format(self.logical_id or self.id))
2021

2122
def report_end(self, duration=NOTHING, has_fatal_errors=NOTHING):
2223

backslash/test.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
from .warning_container import WarningContainer
88
from .lazy_query import LazyQuery
99
from .metadata_holder import MetadataHolder
10+
from .timing_container import TimingContainer
1011

1112

12-
class Test(APIObject, MetadataHolder, ErrorContainer, WarningContainer, Commentable, RelatedEntityContainer):
13+
class Test(APIObject, MetadataHolder, ErrorContainer, WarningContainer, Commentable, RelatedEntityContainer, TimingContainer):
1314

1415
@property
1516
def ui_url(self):
16-
return self.client.url + '#/sessions/{}/tests/{}'.format(self.session_display_id, self.logical_id or self.id)
17+
return self.client.get_ui_url('sessions/{}/tests/{}'.format(self.session_display_id, self.logical_id or self.id))
1718

1819
def report_end(self, duration=NOTHING):
1920
self.client.api.call_function('report_test_end', {'id': self.id, 'duration': duration})
@@ -24,9 +25,6 @@ def mark_skipped(self, reason=None):
2425
def report_interrupted(self):
2526
self.client.api.call_function('report_test_interrupted', {'id': self.id})
2627

27-
def edit_status(self, status):
28-
return self.client.api.call_function('edit_test_status', {'id': self.id, 'status': status})
29-
3028
def query_errors(self):
3129
"""Queries tests of the current session
3230
@@ -39,3 +37,6 @@ def get_session(self):
3937

4038
def get_parent(self):
4139
return self.get_session()
40+
41+
def update_status_description(self, description):
42+
return self.client.api.call_function('update_status_description', {'test_id': self.id, 'description': description})

backslash/timing_container.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
class TimingContainer(object):
2+
3+
def report_timing_start(self, name):
4+
self._report('start', name)
5+
6+
def report_timing_end(self, name):
7+
self._report('end', name)
8+
9+
def _report(self, start_stop, name):
10+
kwargs = {'name': name}
11+
kwargs.update(self._get_identity_kwargs())
12+
self.client.api.call_function('report_timing_{}'.format(start_stop), kwargs) # pylint: disable=no-member
13+
14+
def _get_identity_kwargs(self):
15+
if self.type.lower() == 'session': # pylint: disable=no-member
16+
return {'session_id': self.id} # pylint: disable=no-member
17+
18+
return {'test_id': self.id, 'session_id': self.session_id} # pylint: disable=no-member
19+
20+
def get_timings(self):
21+
return self.client.api.call.get_timings(**self._get_identity_kwargs()) # pylint: disable=no-member

docs/changelog.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
Changelog
22
=========
33

4+
* :release:`2.32.0 <30-10-2017>`
5+
* :feature:`60` Clean up UI URL generation, added ``Backslash.get_ui_url`` helper method
6+
* :feature:`59` Support reporting interruption exceptions to Backslash
7+
* :feature:`58` Support reporting timing metrics
8+
* :feature:`57` Support reporting test status description
49
* :release:`2.31.2 <14-9-2017>`
510
* :bug:`54` Handle cases of detached head correctly when deducing local branch
611
* :release:`2.31.1 <11-9-2017>`
712
* :bug:`53` Use api session when constructing lazy queries
813
* :release:`2.31.0 <10-9-2017>`
9-
* :feature:`52` Support reporting sessions with a specific TTL, marking them for future deletion on the server
14+
* :feature:`52` Support reporting sessions with a specific TTL, marking them for future deletion on the server. This can be also specified in the command-line, by passing ``--session-ttl-days=X``
1015
* :feature:`51` Report local and remote SCM branches if supported
1116
* :release:`2.30.0 <8-8-2017>`
1217
* :feature:`50` Added session_webapp_url property to the Slash plugin

tests/test_api_object.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ def test_ui_url(client, object_type, logical_id, use_logical):
4949
url = obj.ui_url
5050
display_id = logical_id if use_logical else id
5151
if object_type is test.Test:
52-
assert url == client.url + '#/sessions/{}/tests/{}'.format(data['session_display_id'], display_id)
52+
assert url == client.url + '/#/sessions/{}/tests/{}'.format(data['session_display_id'], display_id)
5353
else:
54-
assert url == client.url + '#/{}s/{}'.format(object_type.__name__.lower(), display_id)
54+
assert url == client.url + '/#/{}s/{}'.format(object_type.__name__.lower(), display_id)
5555

5656

5757
@pytest.fixture

tests/test_slash_plugin.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,27 +31,28 @@ def test_exception_distilling_surrounding_code(traceback):
3131

3232

3333
def test_rest_url(installed_plugin, server_url):
34+
installed_plugin.activate()
3435
assert installed_plugin.rest_url == server_url.add_path('rest')
3536

3637
def test_webapp_url(installed_plugin, server_url):
38+
installed_plugin.activate()
3739
expected = server_url
3840
if not expected.endswith('/'):
3941
expected += '/'
4042
expected += '#/'
4143
assert installed_plugin.webapp_url == expected
4244

45+
4346
def test_session_webapp_url_no_session(installed_plugin):
4447
assert installed_plugin.session_webapp_url is None
4548

4649
def test_session_webapp_url_with_session(installed_plugin, server_url):
50+
installed_plugin.activate()
4751
with slash.Session() as s:
4852
url = installed_plugin.session_webapp_url
4953
assert url == '{}/#/sessions/{}'.format(server_url, s.id)
5054

5155

52-
53-
54-
5556
@pytest.fixture
5657
def traceback(error_result):
5758
[e] = error_result.get_errors()
@@ -75,7 +76,7 @@ def test_failing():
7576
@pytest.fixture
7677
def installed_plugin(request, server_url):
7778
from backslash.contrib import slash_plugin
78-
plugin = slash_plugin.BackslashPlugin(url=str(server_url))
79+
plugin = slash_plugin.BackslashPlugin(url=str(server_url), runtoken='blap')
7980

8081
@request.addfinalizer
8182
def cleanup(): # pylint: disable=unused-variable

0 commit comments

Comments
 (0)