source: OpenWorkouts-current/ow/tests/test_utilities.py @ d6f8304

currentfeature/docs
Last change on this file since d6f8304 was d6f8304, checked in by Borja Lopez <borja@…>, 5 years ago

(#52) - screenshots of the workouts maps were corrupted randomly.

Replaced our screenshot_map shell script that was calling chrome headless
directly with some python code running splinter + selenium webdriver.

Using chromedriver in such environment we can first visit the map site
for the given workout, then wait a bit for it to completely load, then
take the screenshot.

I've also removed all traces of screenshot_map from the code.

  • Property mode set to 100644
File size: 11.0 KB
RevLine 
[5ec3a0b]1import os
[2f8a48f]2from datetime import timedelta, datetime
[d6f8304]3from unittest.mock import patch, Mock
[5ec3a0b]4from pyexpat import ExpatError
5from xml.dom.minidom import Element
6
7import pytest
[d6f8304]8from pyramid.testing import DummyRequest
[5ec3a0b]9
[ceae158]10from ow.models.root import OpenWorkouts
11from ow.models.user import User
12from ow.models.workout import Workout
13
[b73ae09]14from ow.utilities import (
15    slugify,
16    GPXMinidomParser,
17    semicircles_to_degrees,
18    degrees_to_semicircles,
19    miles_to_kms,
20    kms_to_miles,
21    meters_to_kms,
22    kms_to_meters,
23    mps_to_kmph,
24    kmph_to_mps,
[2f8a48f]25    save_map_screenshot,
26    timedelta_to_hms,
27    get_week_days
[b73ae09]28)
[5ec3a0b]29
[ceae158]30from ow.tests.helpers import join
31
[5ec3a0b]32
33class TestUtilities(object):
34
[ceae158]35    @pytest.fixture
36    def john(self):
37        john = User(firstname='John', lastname='Doe',
38                    email='john.doe@example.net')
39        john.password = 's3cr3t'
40        return john
41
42    @pytest.fixture
43    def root(self, john):
44        root = OpenWorkouts()
45        root.add_user(john)
46        john['1'] = Workout(
47            duration=timedelta(minutes=60),
48            distance=30
49        )
50        return root
51
[5ec3a0b]52    def test_slugify(self):
53        res = slugify(u'long story SHORT      ')
54        assert res == u'long-story-short'
55        res = slugify(u'bla \u03ba\u03b1\u03bb\u03ac \u03c0\u03b1\u03c2')
56        assert res == u'bla-kala-pas'
57
58    def test_slugify_special_chars(self):
59        res = slugify(u'(r)-[i]\u00AE')
60        assert res == u'r-i-r'
61
[b73ae09]62    def test_semicircles_to_degrees(self):
63        assert semicircles_to_degrees(10) == 10 * (180 / pow(2, 31))
64
65    def test_degrees_to_semicircles(self):
66        assert degrees_to_semicircles(10) == 10 * (pow(2, 31) / 180)
67
68    def test_miles_to_kms(self):
69        assert miles_to_kms(100) == 100 / 0.62137119
70
71    def test_kms_to_miles(self):
72        assert kms_to_miles(100) == 100 * 0.62137119
73
74    def test_meters_to_kms(self):
75        assert meters_to_kms(1000) == 1
76
77    def test_kms_to_meters(self):
78        assert kms_to_meters(1) == 1000
79
80    def test_mps_to_kmph(self):
81        assert mps_to_kmph(5) == 5 * 3.6
82
83    def test_kmph_to_mps(self):
84        assert kmph_to_mps(30) == 30 * 0.277778
85
[d6f8304]86    @patch('ow.utilities.shutil')
[ceae158]87    @patch('ow.utilities.os')
[d6f8304]88    @patch('ow.utilities.Browser')
89    def test_save_map_screenshot_no_gpx(
90            self, Browser, os, shutil, root, john):
91        request = DummyRequest()
92        saved = save_map_screenshot(john['1'], request)
[ceae158]93        assert not saved
[d6f8304]94        assert not Browser.called
[ceae158]95        assert not os.path.abspath.called
96        assert not os.path.dirname.called
97        assert not os.path.join.called
98        assert not os.path.exists.called
99        assert not os.makedirs.called
[d6f8304]100        assert not shutil.move.called
[ceae158]101        # even having a fit tracking file, nothing is done
102        john['1'].tracking_file = 'faked fit file'
103        john['1'].tracking_filetype = 'fit'
[d6f8304]104        saved = save_map_screenshot(john['1'], request)
[ceae158]105        assert not saved
[d6f8304]106        assert not Browser.called
[ceae158]107        assert not os.path.abspath.called
108        assert not os.path.dirname.called
109        assert not os.path.join.called
110        assert not os.path.exists.called
111        assert not os.makedirs.called
[d6f8304]112        assert not shutil.move.called
[ceae158]113
[d6f8304]114    @patch('ow.utilities.shutil')
[ceae158]115    @patch('ow.utilities.os')
[d6f8304]116    @patch('ow.utilities.Browser')
117    def test_save_map_screenshot_with_gpx(
118            self, Browser, os, shutil, root, john):
119        request = DummyRequest()
120        browser = Mock()
121        Browser.return_value = browser
[ceae158]122        os.path.abspath.return_value = 'current_dir'
123        os.path.join.side_effect = join
124        # This mimics what happens when the directory for this user map
125        # screenshots does not exist, which means we don'have to create one
126        # (calling os.makedirs)
127        os.path.exists.return_value = False
128
[d6f8304]129        map_url = request.resource_url(john['1'], 'map')
130
[ceae158]131        john['1'].tracking_file = 'faked gpx content'
132        john['1'].tracking_filetype = 'gpx'
[d6f8304]133        saved = save_map_screenshot(john['1'], request)
[ceae158]134        assert saved
[d6f8304]135        Browser.assert_called_once_with('chrome', headless=True)
136        browser.driver.set_window_size.assert_called_once_with(1300, 436)
137        browser.visit.assert_called_once_with(map_url)
138        browser.screenshot.assert_called_once
[ceae158]139        os.path.abspath.assert_called_once
140        assert os.path.dirname.called
[d6f8304]141        assert os.path.join.call_count == 2
[ceae158]142        assert os.path.exists.called
143        assert os.makedirs.called
[d6f8304]144        os.shutil.move.assert_called_once
[ceae158]145
[d6f8304]146    @patch('ow.utilities.shutil')
[ceae158]147    @patch('ow.utilities.os')
[d6f8304]148    @patch('ow.utilities.Browser')
[ceae158]149    def test_save_map_screenshot_with_gpx_makedirs(
[d6f8304]150            self, Browser, os, shutil, root, john):
151        request = DummyRequest()
152        browser = Mock()
153        Browser.return_value = browser
[ceae158]154        os.path.abspath.return_value = 'current_dir'
155        os.path.join.side_effect = join
156        # If os.path.exists returns True, makedirs is not called
157        os.path.exists.return_value = True
158
[d6f8304]159        map_url = request.resource_url(john['1'], 'map')
160
[ceae158]161        john['1'].tracking_file = 'faked gpx content'
162        john['1'].tracking_filetype = 'gpx'
[d6f8304]163        saved = save_map_screenshot(john['1'], request)
[ceae158]164        assert saved
[d6f8304]165        Browser.assert_called_once_with('chrome', headless=True)
166        browser.driver.set_window_size.assert_called_once_with(1300, 436)
167        browser.visit.assert_called_once_with(map_url)
168        browser.screenshot.assert_called_once
[ceae158]169        os.path.abspath.assert_called_once
170        assert os.path.dirname.called
[d6f8304]171        assert os.path.join.call_count == 2
[ceae158]172        assert os.path.exists.called
173        assert not os.makedirs.called
[d6f8304]174        os.shutil.move.assert_called_once
[ceae158]175
[2f8a48f]176    def test_timedelta_to_hms(self):
177        value = timedelta(seconds=0)
178        assert timedelta_to_hms(value) == (0, 0, 0)
179        value = timedelta(seconds=3600)
180        assert timedelta_to_hms(value) == (1, 0, 0)
181        value = timedelta(seconds=3900)
182        assert timedelta_to_hms(value) == (1, 5, 0)
183        value = timedelta(seconds=3940)
184        assert timedelta_to_hms(value) == (1, 5, 40)
185        value = timedelta(seconds=4)
186        assert timedelta_to_hms(value) == (0, 0, 4)
187        value = timedelta(seconds=150)
188        assert timedelta_to_hms(value) == (0, 2, 30)
189        # try now something that is not a timedelta
190        with pytest.raises(AttributeError):
191            timedelta_to_hms('not a timedelta')
192
193    def test_week_days(self):
194        # get days from a monday, week starting on monday
195        days = get_week_days(datetime(2019, 1, 21))
196        assert len(days) == 7
197        matches = [
198            [days[0], datetime(2019, 1, 21)],
199            [days[1], datetime(2019, 1, 22)],
200            [days[2], datetime(2019, 1, 23)],
201            [days[3], datetime(2019, 1, 24)],
202            [days[4], datetime(2019, 1, 25)],
203            [days[5], datetime(2019, 1, 26)],
204            [days[6], datetime(2019, 1, 27)]
205        ]
206        for m in matches:
207            assert m[0] == m[1]
208        # get days from a wednesday, week starting on monday
209        days = get_week_days(datetime(2019, 1, 23))
210        assert len(days) == 7
211        matches = [
212            [days[0], datetime(2019, 1, 21)],
213            [days[1], datetime(2019, 1, 22)],
214            [days[2], datetime(2019, 1, 23)],
215            [days[3], datetime(2019, 1, 24)],
216            [days[4], datetime(2019, 1, 25)],
217            [days[5], datetime(2019, 1, 26)],
218            [days[6], datetime(2019, 1, 27)]
219        ]
220        for m in matches:
221            assert m[0] == m[1]
222        # get days from a monday, but week starting on sunday now
223        days = get_week_days(datetime(2019, 1, 21), start_day=0)
224        assert len(days) == 7
225        matches = [
226            [days[0], datetime(2019, 1, 20)],
227            [days[1], datetime(2019, 1, 21)],
228            [days[2], datetime(2019, 1, 22)],
229            [days[3], datetime(2019, 1, 23)],
230            [days[4], datetime(2019, 1, 24)],
231            [days[5], datetime(2019, 1, 25)],
232            [days[6], datetime(2019, 1, 26)]
233        ]
234        for m in matches:
235            assert m[0] == m[1]
236
[5ec3a0b]237
238class TestGPXParseMinidom(object):
239
240    def gpx_file(self, filename):
241        """
242        Return the full path to the given filename from the available fixtures
243        """
244        here = os.path.abspath(os.path.dirname(__file__))
245        path = os.path.join(here, 'fixtures', filename)
246        return path
247
248    def test_load_gpx_invalid(self):
249        gpx_file = self.gpx_file('invalid.gpx')
250        parser = GPXMinidomParser(gpx_file)
251        with pytest.raises(ExpatError):
252            parser.load_gpx()
253        assert parser.gpx is None
254
255    gpx_files = [
256        ('empty.gpx', Element),
257        ('20131013.gpx', Element),  # GPX 1.0 file
258        ('20160129-with-extensions.gpx', Element),  # GPX 1.1 file with ext.
259    ]
260
261    @pytest.mark.parametrize(('filename', 'expected'), gpx_files)
262    def test_load_gpx(self, filename, expected):
263        """
264        Loading valid gpx files ends in the gpx attribute of the parser
265        being a xml.dom.minidom.Element object, not matter if the gpx file
266        is empty or a 1.0/1.1 gpx file.
267        """
268        gpx_file = self.gpx_file(filename)
269        parser = GPXMinidomParser(gpx_file)
270        parser.load_gpx()
271        assert isinstance(parser.gpx, expected)
272
273    def test_parse_tracks_empty_gpx(self):
274        gpx_file = self.gpx_file('empty.gpx')
275        parser = GPXMinidomParser(gpx_file)
276        parser.load_gpx()
277        parser.parse_tracks()
278        assert parser.tracks == {}
279
280    def test_parse_tracks_1_0_gpx(self):
281        """
282        Parsing a GPX 1.0 file with no extensions. The points in the track
283        contain keys for the well-known extensions (hr, cad, atemp), but their
284        values are None
285        """
286        gpx_file = self.gpx_file('20131013.gpx')
287        parser = GPXMinidomParser(gpx_file)
288        parser.load_gpx()
289        parser.parse_tracks()
290        keys = list(parser.tracks.keys())
291        assert keys == [u'A ride I will never forget']
292        point = parser.tracks[keys[0]][0]
293        data = ['lat', 'lon', 'ele', 'time']
294        for d in data:
295            assert point[d] is not None
296        extensions = ['hr', 'cad', 'atemp']
297        for e in extensions:
298            assert point[e] is None
299
300    def test_parse_tracks_1_1_gpx(self):
301        """
302        Parsing a GPX 1.1 file with extensions. The points in the track contain
303        keys for the well-known extensions (hr, cad, atemp), with the values
304        taken from the gpx file (although we test only that they are not None)
305        """
306        gpx_file = self.gpx_file('20160129-with-extensions.gpx')
307        parser = GPXMinidomParser(gpx_file)
308        parser.load_gpx()
309        parser.parse_tracks()
310        keys = list(parser.tracks.keys())
311        assert keys == [
312            u'Cota counterclockwise + end bonus']
313        point = parser.tracks[keys[0]][0]
314        data = ['lat', 'lon', 'ele', 'time']
315        for d in data:
316            assert point[d] is not None
317        extensions = ['hr', 'cad', 'atemp']
318        for e in extensions:
319            assert point[e] is not None
Note: See TracBrowser for help on using the repository browser.