Merge lp:camera-app/staging into lp:camera-app

Proposed by Florian Boucault
Status: Merged
Approved by: Florian Boucault
Approved revision: 696
Merged at revision: 646
Proposed branch: lp:camera-app/staging
Merge into: lp:camera-app
Diff against target: 1271 lines (+814/-86)
19 files modified
CameraApp/advancedcamerasettings.cpp (+30/-26)
CameraApp/advancedcamerasettings.h (+4/-6)
ViewFinderGeometry.qml (+2/-2)
ViewFinderOverlay.qml (+11/-18)
ViewFinderView.qml (+2/-3)
camera-app.qml (+2/-2)
debian/control (+1/-0)
tests/autopilot/camera_app/emulators/main_window.py (+39/-11)
tests/autopilot/camera_app/emulators/panel.py (+41/-10)
tests/autopilot/camera_app/tests/__init__.py (+3/-0)
tests/autopilot/camera_app/tests/test_focus.py (+6/-6)
tests/autopilot/camera_app/tests/test_options.py (+2/-2)
tests/autopilot/camera_app/ubuntu_system_tests/__init__.py (+19/-0)
tests/autopilot/camera_app/ubuntu_system_tests/helpers/__init__.py (+19/-0)
tests/autopilot/camera_app/ubuntu_system_tests/helpers/backup_restore_fixture.py (+135/-0)
tests/autopilot/camera_app/ubuntu_system_tests/helpers/camera/__init__.py (+19/-0)
tests/autopilot/camera_app/ubuntu_system_tests/helpers/camera/fixture_setup.py (+58/-0)
tests/autopilot/camera_app/ubuntu_system_tests/helpers/file_system.py (+359/-0)
tests/autopilot/camera_app/ubuntu_system_tests/helpers/sqlite.py (+62/-0)
To merge this branch: bzr merge lp:camera-app/staging
Reviewer Review Type Date Requested Status
Ubuntu Phablet Team Pending
Review via email: mp+292495@code.launchpad.net

Commit message

New release:
- Reverted revision 637 that was causing a regression where the controls could not be opened in landscape orientation anymore.
- Set Camera.position to switch front/back cameras instead of AdvancedCameraSettings and hardcoded camera indexes

Description of the change

New release:
- Reverted revision 637 that was causing a regression where the controls could not be opened in landscape orientation anymore.
- Set Camera.position to switch front/back cameras instead of AdvancedCameraSettings and hardcoded camera indexes

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CameraApp/advancedcamerasettings.cpp'
2--- CameraApp/advancedcamerasettings.cpp 2016-01-28 09:52:25 +0000
3+++ CameraApp/advancedcamerasettings.cpp 2016-04-21 10:43:56 +0000
4@@ -36,7 +36,6 @@
5
6 AdvancedCameraSettings::AdvancedCameraSettings(QObject *parent) :
7 QObject(parent),
8- m_activeCameraIndex(0),
9 m_cameraObject(0),
10 m_camera(0),
11 m_deviceSelector(0),
12@@ -45,6 +44,7 @@
13 m_cameraExposureControl(0),
14 m_imageEncoderControl(0),
15 m_videoEncoderControl(0),
16+ m_cameraInfoControl(0),
17 m_hdrEnabled(false)
18 {
19 }
20@@ -183,16 +183,23 @@
21 return videoEncoderControl;
22 }
23
24+QCameraInfoControl* AdvancedCameraSettings::cameraInfoControlFromCamera(QCamera *camera) const
25+{
26+ QMediaControl *control = mediaControlFromCamera(camera, QCameraInfoControl_iid);
27+ QCameraInfoControl *infoControl = qobject_cast<QCameraInfoControl*>(control);
28+
29+ if (infoControl == 0) {
30+ qWarning() << "No info control support";
31+ }
32+
33+ return infoControl;
34+}
35+
36 QObject* AdvancedCameraSettings::camera() const
37 {
38 return m_cameraObject;
39 }
40
41-int AdvancedCameraSettings::activeCameraIndex() const
42-{
43- return m_activeCameraIndex;
44-}
45-
46 void AdvancedCameraSettings::setCamera(QObject *cameraObject)
47 {
48 if (cameraObject != m_cameraObject) {
49@@ -210,15 +217,25 @@
50
51 QVideoDeviceSelectorControl* selector = selectorFromCamera(m_camera);
52 m_deviceSelector = selector;
53- if (selector) {
54- m_deviceSelector->setSelectedDevice(m_activeCameraIndex);
55- }
56+ connect(m_deviceSelector, SIGNAL(selectedDeviceChanged(int)),
57+ this, SLOT(onSelectedDeviceChanged(int)));
58 }
59
60 Q_EMIT cameraChanged();
61 }
62 }
63
64+void AdvancedCameraSettings::onSelectedDeviceChanged(int index)
65+{
66+ Q_UNUSED(index);
67+
68+ Q_EMIT resolutionChanged();
69+ Q_EMIT maximumResolutionChanged();
70+ Q_EMIT fittingResolutionChanged();
71+ Q_EMIT hasFlashChanged();
72+ Q_EMIT videoSupportedResolutionsChanged();
73+}
74+
75 void AdvancedCameraSettings::readCapabilities()
76 {
77 m_viewFinderControl = viewfinderFromCamera(m_camera);
78@@ -249,6 +266,7 @@
79
80 m_imageEncoderControl = imageEncoderControlFromCamera(m_camera);
81 m_videoEncoderControl = videoEncoderControlFromCamera(m_camera);
82+ m_cameraInfoControl = cameraInfoControlFromCamera(m_camera);
83
84 Q_EMIT resolutionChanged();
85 Q_EMIT maximumResolutionChanged();
86@@ -267,22 +285,6 @@
87 }
88 }
89
90-void AdvancedCameraSettings::setActiveCameraIndex(int index)
91-{
92- if (index != m_activeCameraIndex) {
93- m_activeCameraIndex = index;
94- if (m_deviceSelector) {
95- m_deviceSelector->setSelectedDevice(m_activeCameraIndex);
96- }
97- Q_EMIT activeCameraIndexChanged();
98- Q_EMIT resolutionChanged();
99- Q_EMIT maximumResolutionChanged();
100- Q_EMIT fittingResolutionChanged();
101- Q_EMIT hasFlashChanged();
102- Q_EMIT videoSupportedResolutionsChanged();
103- }
104-}
105-
106 QSize AdvancedCameraSettings::resolution() const
107 {
108 if (m_viewFinderControl != 0) {
109@@ -417,7 +419,9 @@
110 // When using the front camera on krillin, using resolution 640x480 does
111 // not work properly and results in stretched videos. Remove it from
112 // the list of supported resolutions.
113- if (activeCameraIndex() == 1 && size.width() == 640 && size.height() == 480) {
114+ QString currentDeviceName = m_deviceSelector->deviceName(m_deviceSelector->selectedDevice());
115+ if (m_cameraInfoControl->cameraPosition(currentDeviceName) == QCamera::FrontFace &&
116+ size.width() == 640 && size.height() == 480) {
117 continue;
118 }
119 sizesAsStrings.append(QString("%1x%2").arg(size.width()).arg(size.height()));
120
121=== modified file 'CameraApp/advancedcamerasettings.h'
122--- CameraApp/advancedcamerasettings.h 2016-01-05 13:08:28 +0000
123+++ CameraApp/advancedcamerasettings.h 2016-04-21 10:43:56 +0000
124@@ -23,6 +23,7 @@
125 #include <QObject>
126 #include <QMultimedia>
127 #include <QtMultimedia/QCamera>
128+#include <QtMultimedia/QCameraInfoControl>
129 #include <QtMultimedia/QVideoDeviceSelectorControl>
130 #include <QtMultimedia/QCameraViewfinderSettingsControl>
131 #include <QtMultimedia/QCameraExposureControl>
132@@ -37,8 +38,6 @@
133 {
134 Q_OBJECT
135 Q_PROPERTY (QObject* camera READ camera WRITE setCamera NOTIFY cameraChanged)
136- Q_PROPERTY (int activeCameraIndex READ activeCameraIndex WRITE setActiveCameraIndex
137- NOTIFY activeCameraIndexChanged)
138 Q_PROPERTY (QSize resolution READ resolution NOTIFY resolutionChanged)
139 Q_PROPERTY (QSize imageCaptureResolution READ imageCaptureResolution)
140 Q_PROPERTY (QSize videoRecorderResolution READ videoRecorderResolution)
141@@ -53,9 +52,7 @@
142 public:
143 explicit AdvancedCameraSettings(QObject *parent = 0);
144 QObject* camera() const;
145- int activeCameraIndex() const;
146 void setCamera(QObject* camera);
147- void setActiveCameraIndex(int index);
148 QSize resolution() const;
149 QSize imageCaptureResolution() const;
150 QSize videoRecorderResolution() const;
151@@ -73,7 +70,6 @@
152
153 Q_SIGNALS:
154 void cameraChanged();
155- void activeCameraIndexChanged();
156 void resolutionChanged();
157 void maximumResolutionChanged();
158 void fittingResolutionChanged();
159@@ -86,6 +82,7 @@
160 private Q_SLOTS:
161 void onCameraStateChanged();
162 void onExposureValueChanged(int parameter);
163+ void onSelectedDeviceChanged(int index);
164
165 private:
166 QVideoDeviceSelectorControl* selectorFromCamera(QCamera *camera) const;
167@@ -97,17 +94,18 @@
168 QMediaControl* mediaControlFromCamera(QCamera *camera, const char* iid) const;
169 QImageEncoderControl* imageEncoderControlFromCamera(QCamera *camera) const;
170 QVideoEncoderSettingsControl* videoEncoderControlFromCamera(QCamera *camera) const;
171+ QCameraInfoControl* cameraInfoControlFromCamera(QCamera *camera) const;
172
173 QObject* m_cameraObject;
174 QCamera* m_camera;
175 QVideoDeviceSelectorControl* m_deviceSelector;
176- int m_activeCameraIndex;
177 QCameraViewfinderSettingsControl* m_viewFinderControl;
178 QCameraControl* m_cameraControl;
179 QCameraFlashControl* m_cameraFlashControl;
180 QCameraExposureControl* m_cameraExposureControl;
181 QImageEncoderControl* m_imageEncoderControl;
182 QVideoEncoderSettingsControl* m_videoEncoderControl;
183+ QCameraInfoControl* m_cameraInfoControl;
184 bool m_hdrEnabled;
185 };
186
187
188=== modified file 'ViewFinderGeometry.qml'
189--- ViewFinderGeometry.qml 2015-11-03 14:23:10 +0000
190+++ ViewFinderGeometry.qml 2016-04-21 10:43:56 +0000
191@@ -24,9 +24,9 @@
192 property int viewFinderWidth;
193 property int viewFinderOrientation;
194
195- property int __cameraWidth: Math.abs(viewFinderOrientation) == 90 ?
196+ property int __cameraWidth: Math.abs(viewFinderOrientation) == 90 || Math.abs(viewFinderOrientation) == 270 ?
197 cameraResolution.height : cameraResolution.width
198- property int __cameraHeight: Math.abs(viewFinderOrientation) == 90 ?
199+ property int __cameraHeight: Math.abs(viewFinderOrientation) == 90 || Math.abs(viewFinderOrientation) == 270 ?
200 cameraResolution.width : cameraResolution.height
201
202 property real widthScale: viewFinderWidth / __cameraWidth
203
204=== modified file 'ViewFinderOverlay.qml'
205--- ViewFinderOverlay.qml 2016-03-23 14:49:34 +0000
206+++ ViewFinderOverlay.qml 2016-04-21 10:43:56 +0000
207@@ -55,8 +55,7 @@
208 property bool playShutterSound: true
209 // FIXME: stores the resolution selected for 2 cameras. Instead it should:
210 // - support any number of cameras
211- // - not rely on the camera index but on Camera.deviceId
212- // Ref.: http://doc.qt.io/qt-5/qml-qtmultimedia-camera.html#deviceId-prop
213+ // - not assume that camera.deviceId is a string containing an integer
214 property string photoResolution0
215 property string photoResolution1
216
217@@ -99,7 +98,7 @@
218 Binding {
219 target: camera.imageCapture
220 property: "resolution"
221- value: settings["photoResolution" + camera.advanced.activeCameraIndex]
222+ value: settings["photoResolution" + camera.deviceId]
223 }
224
225 Connections {
226@@ -220,9 +219,9 @@
227 }
228
229 // If resolution setting is not supported select the resolution automatically
230- var photoResolution = settings["photoResolution" + camera.advanced.activeCameraIndex];
231+ var photoResolution = settings["photoResolution" + camera.deviceId];
232 if (!isResolutionAnOption(photoResolution)) {
233- settings["photoResolution" + camera.advanced.activeCameraIndex] = getAutomaticResolution();
234+ settings["photoResolution" + camera.deviceId] = getAutomaticResolution();
235 }
236 }
237
238@@ -261,18 +260,18 @@
239 }
240
241 Connections {
242- target: camera.advanced
243- onActiveCameraIndexChanged: {
244- var hasPhotoResolutionSetting = (settings["photoResolution" + camera.advanced.activeCameraIndex] != "")
245+ target: camera
246+ onDeviceIdChanged: {
247+ var hasPhotoResolutionSetting = (settings["photoResolution" + camera.deviceId] != "")
248 // FIXME: use camera.advanced.imageCaptureResolution instead of camera.imageCapture.resolution
249 // because the latter is not updated when the backend changes the resolution
250- settings["photoResolution" + camera.advanced.activeCameraIndex] = sizeToString(camera.advanced.imageCaptureResolution);
251+ settings["photoResolution" + camera.deviceId] = sizeToString(camera.advanced.imageCaptureResolution);
252 settings.videoResolution = sizeToString(camera.advanced.videoRecorderResolution);
253 updateResolutionOptions();
254
255 // If no resolution has ever been chosen, select one automatically
256 if (!hasPhotoResolutionSetting) {
257- settings["photoResolution" + camera.advanced.activeCameraIndex] = getAutomaticResolution();
258+ settings["photoResolution" + camera.deviceId] = getAutomaticResolution();
259 }
260 }
261 }
262@@ -558,7 +557,7 @@
263 ListModel {
264 id: photoResolutionOptionsModel
265
266- property string settingsProperty: "photoResolution" + camera.advanced.activeCameraIndex
267+ property string settingsProperty: "photoResolution" + camera.deviceId
268 property string icon: ""
269 property string label: sizeToAspectRatio(stringToSize(settings[settingsProperty]))
270 property bool isToggle: false
271@@ -916,14 +915,8 @@
272
273 MouseArea {
274 id: manualFocusMouseArea
275+ anchors.fill: parent
276 objectName: "manualFocusMouseArea"
277- anchors {
278- fill: parent
279- // Pinch gestures need more clearance at the edges of the screen, but
280- // tap to focus should be safe all the way to the edges themselves instead.
281- leftMargin: -bottomEdgeIndicators.height
282- rightMargin: -bottomEdgeIndicators.height
283- }
284 enabled: camera.focus.isFocusPointModeSupported(Camera.FocusPointCustom) &&
285 !camera.photoCaptureInProgress && !camera.timedCaptureInProgress
286 onClicked: {
287
288=== modified file 'ViewFinderView.qml'
289--- ViewFinderView.qml 2016-03-23 14:49:34 +0000
290+++ ViewFinderView.qml 2016-04-21 10:43:56 +0000
291@@ -56,7 +56,7 @@
292 id: camera
293 captureMode: Camera.CaptureStillImage
294 cameraState: Camera.UnloadedState
295- StateSaver.properties: "captureMode"
296+ StateSaver.properties: "captureMode, position"
297 property bool failedToConnect: false
298
299 function manualFocus(x, y) {
300@@ -89,7 +89,6 @@
301 property AdvancedCameraSettings advanced: AdvancedCameraSettings {
302 id: advancedCamera
303 camera: camera
304- StateSaver.properties: "activeCameraIndex"
305 }
306
307 /* Use only digital zoom for now as it's what phone cameras mostly use.
308@@ -171,7 +170,7 @@
309 viewFinder.width = 1;
310 viewFinder.height = 1;
311 camera.cameraState = Camera.LoadedState;
312- camera.advanced.activeCameraIndex = (camera.advanced.activeCameraIndex === 0) ? 1 : 0;
313+ camera.position = (camera.position === Camera.FrontFace) ? Camera.BackFace : Camera.FrontFace;
314 decideCameraState();
315 viewFinderSwitcherRotation.angle = 180;
316 }
317
318=== modified file 'camera-app.qml'
319--- camera-app.qml 2016-03-23 14:49:34 +0000
320+++ camera-app.qml 2016-04-21 10:43:56 +0000
321@@ -26,8 +26,8 @@
322 Window {
323 id: main
324 objectName: "main"
325- width: height * viewFinderView.aspectRatio
326- height: units.gu(80)
327+ width: Math.min(Screen.width, height * viewFinderView.aspectRatio)
328+ height: Math.min(Screen.height, units.gu(80))
329 color: "black"
330 title: "Camera"
331 // special flag only supported by Unity8/MIR so far that hides the shell's
332
333=== modified file 'debian/control'
334--- debian/control 2016-03-09 10:29:26 +0000
335+++ debian/control 2016-04-21 10:43:56 +0000
336@@ -65,5 +65,6 @@
337 python3-autopilot,
338 python3-wand,
339 python3-mediainfodll,
340+ python3-fixtures,
341 Description: Test package for the camera app
342 Autopilot tests for the camera-app package
343
344=== modified file 'tests/autopilot/camera_app/emulators/main_window.py'
345--- tests/autopilot/camera_app/emulators/main_window.py 2016-03-23 15:42:31 +0000
346+++ tests/autopilot/camera_app/emulators/main_window.py 2016-04-21 10:43:56 +0000
347@@ -196,15 +196,29 @@
348 view_switcher.settling.wait_for(False)
349 view_switcher.switching.wait_for(False)
350 viewfinder.inView.wait_for(True)
351+
352 x, y = view_switcher.x, view_switcher.y
353 w, h = view_switcher.width, view_switcher.height
354-
355- tx = x + (w // 2)
356- ty = y + (h // 2)
357+ center_x = x + (w // 2)
358+ center_y = y + (h // 2)
359
360 # FIXME: a rate higher than 1 does not always make view_switcher move
361- self.app.pointing_device.drag(tx, ty, x, ty, rate=1,
362- time_between_events=0.0001)
363+ if view_switcher.state == "PORTRAIT":
364+ self.app.pointing_device.drag(center_x, center_y,
365+ x, center_y,
366+ rate=1, time_between_events=0.0001)
367+ elif view_switcher.state == "LANDSCAPE":
368+ self.app.pointing_device.drag(center_x, y + (3 * h // 4),
369+ center_x, center_y,
370+ rate=1, time_between_events=0.0001)
371+ elif view_switcher.state == "INVERTED_LANDSCAPE":
372+ self.app.pointing_device.drag(center_x, y + (h // 4),
373+ center_x, center_y,
374+ rate=1, time_between_events=0.0001)
375+ else:
376+ self.app.pointing_device.drag(center_x, center_y,
377+ x + w - 1, center_y,
378+ rate=1, time_between_events=0.0001)
379
380 testCase.assertThat(viewfinder.inView, Eventually(Equals(False)))
381 view_switcher.settling.wait_for(False)
382@@ -218,16 +232,30 @@
383 view_switcher.settling.wait_for(False)
384 view_switcher.switching.wait_for(False)
385 viewfinder.inView.wait_for(False)
386+
387 x, y = view_switcher.x, view_switcher.y
388 w, h = view_switcher.width, view_switcher.height
389-
390- tx = x + (w // 2)
391- ty = y + (h // 2)
392+ center_x = x + (w // 2)
393+ center_y = y + (h // 2)
394
395 # FIXME: a rate higher than 1 does not always make view_switcher move
396- self.app.pointing_device.drag(
397- tx, ty, (tx + view_switcher.width // 2), ty, rate=1,
398- time_between_events=0.0001)
399+ if view_switcher.state == "PORTRAIT":
400+ self.app.pointing_device.drag(center_x, center_y,
401+ x + w - 1, center_y,
402+ rate=1, time_between_events=0.0001)
403+ elif view_switcher.state == "LANDSCAPE":
404+ self.app.pointing_device.drag(center_x, y + (h // 4),
405+ center_x, center_y,
406+ rate=1, time_between_events=0.0001)
407+ elif view_switcher.state == "INVERTED_LANDSCAPE":
408+ self.app.pointing_device.drag(center_x, y + (3 * h // 4),
409+ center_x, center_y,
410+ rate=1, time_between_events=0.0001)
411+ else:
412+ self.app.pointing_device.drag(center_x, center_y,
413+ x, center_y,
414+ rate=1, time_between_events=0.0001)
415+
416 testCase.assertThat(viewfinder.inView, Eventually(Equals(True)))
417 view_switcher.settling.wait_for(False)
418 view_switcher.switching.wait_for(False)
419
420=== modified file 'tests/autopilot/camera_app/emulators/panel.py'
421--- tests/autopilot/camera_app/emulators/panel.py 2016-03-18 12:24:36 +0000
422+++ tests/autopilot/camera_app/emulators/panel.py 2016-04-21 10:43:56 +0000
423@@ -35,13 +35,29 @@
424
425 def _drag_to_open(self):
426 x, y, _, _ = self.globalRect
427- line_x = x + self.width * 0.50
428- start_y = y + self.height - 1
429- stop_y = y
430+ center_x = x + self.width * 0.50
431+ center_y = y + self.height * 0.50
432+
433+ view_switcher = self.get_root_instance().wait_select_single(
434+ objectName="viewSwitcher")
435
436 # FIXME: a rate higher than 1 does not always make panel move
437- self.pointing_device.drag(line_x, start_y, line_x, stop_y, rate=1,
438- time_between_events=0.0001)
439+ if view_switcher.state == "PORTRAIT":
440+ self.pointing_device.drag(center_x, y + self.height - 1,
441+ center_x, y, rate=1,
442+ time_between_events=0.0001)
443+ elif view_switcher.state == "LANDSCAPE":
444+ self.pointing_device.drag(0, center_y,
445+ self.height - 1, center_y, rate=1,
446+ time_between_events=0.0001)
447+ elif view_switcher.state == "INVERTED_LANDSCAPE":
448+ self.pointing_device.drag(x + self.height - 1, center_y,
449+ x, center_y, rate=1,
450+ time_between_events=0.0001)
451+ else:
452+ self.pointing_device.drag(center_x, y + self.height - 1,
453+ center_x, y, rate=1,
454+ time_between_events=0.0001)
455
456 @autopilot_logging.log_action(logger.info)
457 def close(self):
458@@ -55,10 +71,25 @@
459
460 def _drag_to_close(self):
461 x, y, _, _ = self.globalRect
462- line_x = x + self.width - 1
463- start_y = y
464- stop_y = y + self.height - 1
465+ center_y = y + self.height * 0.50
466+
467+ view_switcher = self.get_root_instance().wait_select_single(
468+ objectName="viewSwitcher")
469
470 # FIXME: a rate higher than 1 does not always make panel move
471- self.pointing_device.drag(line_x, start_y, line_x, stop_y, rate=1,
472- time_between_events=0.0001)
473+ if view_switcher.state == "PORTRAIT":
474+ self.pointing_device.drag(x, y,
475+ x, y + self.height - 1, rate=1,
476+ time_between_events=0.0001)
477+ elif view_switcher.state == "LANDSCAPE":
478+ self.pointing_device.drag(self.height - 1, center_y,
479+ 0, center_y, rate=1,
480+ time_between_events=0.0001)
481+ elif view_switcher.state == "INVERTED_LANDSCAPE":
482+ self.pointing_device.drag(x, center_y,
483+ x + self.height - 1, center_y, rate=1,
484+ time_between_events=0.0001)
485+ else:
486+ self.pointing_device.drag(x, y,
487+ x, y + self.height - 1, rate=1,
488+ time_between_events=0.0001)
489
490=== modified file 'tests/autopilot/camera_app/tests/__init__.py'
491--- tests/autopilot/camera_app/tests/__init__.py 2016-03-23 10:30:32 +0000
492+++ tests/autopilot/camera_app/tests/__init__.py 2016-04-21 10:43:56 +0000
493@@ -17,6 +17,8 @@
494 from autopilot.testcase import AutopilotTestCase
495
496 from camera_app.emulators.main_window import MainWindow
497+from camera_app.ubuntu_system_tests.helpers.camera.fixture_setup import (
498+ SetCameraAccessRequests)
499
500
501 CUSTOM_PROXY_OBJECT_BASE = ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase
502@@ -41,6 +43,7 @@
503 sample_dir = resource_filename('camera_app', 'data')
504
505 def setUp(self):
506+ self.useFixture(SetCameraAccessRequests())
507 # Remove configuration file
508 config_file = os.path.expanduser(
509 "~/.config/com.ubuntu.camera/com.ubuntu.camera.conf")
510
511=== modified file 'tests/autopilot/camera_app/tests/test_focus.py'
512--- tests/autopilot/camera_app/tests/test_focus.py 2016-03-18 12:04:56 +0000
513+++ tests/autopilot/camera_app/tests/test_focus.py 2016-04-21 10:43:56 +0000
514@@ -50,12 +50,12 @@
515 # Then try on the side edges and top edge to verify they
516 # are focusable too
517 self.verify_focus_ring_after_click_at(focus_ring,
518- geometry.globalRect.x + 1, mid_y)
519+ geometry.globalRect.x + 100,
520+ mid_y)
521 self.verify_focus_ring_after_click_at(focus_ring,
522 geometry.globalRect.x +
523- geometry.globalRect.width - 1,
524+ geometry.globalRect.width - 100,
525 mid_y)
526- self.verify_focus_ring_after_click_at(focus_ring, mid_x, 1)
527
528 # Switch cameras, wait for camera to settle, and try again
529 self.pointing_device.move_to_object(switch_cameras)
530@@ -68,12 +68,12 @@
531 # Then try on the left, right and above the center to verify they
532 # are focusable too
533 self.verify_focus_ring_after_click_at(focus_ring,
534- geometry.globalRect.x + 1, mid_y)
535+ geometry.globalRect.x + 100,
536+ mid_y)
537 self.verify_focus_ring_after_click_at(focus_ring,
538 geometry.globalRect.x +
539- geometry.globalRect.width - 1,
540+ geometry.globalRect.width - 100,
541 mid_y)
542- self.verify_focus_ring_after_click_at(focus_ring, mid_x, 1)
543
544 @unittest.skipIf(model() == 'Galaxy Nexus', 'Unusable with Mir on maguro')
545 def test_focus_invalid(self):
546
547=== modified file 'tests/autopilot/camera_app/tests/test_options.py'
548--- tests/autopilot/camera_app/tests/test_options.py 2016-03-18 12:04:56 +0000
549+++ tests/autopilot/camera_app/tests/test_options.py 2016-04-21 10:43:56 +0000
550@@ -41,10 +41,10 @@
551 # check overlay is opened
552 self.assertThat(bottom_edge.opened, Eventually(Equals(True)))
553
554- # tap on the bottom of the viewfinder to close overlay
555+ # tap on the top of the viewfinder to close overlay
556 viewfinder = self.main_window.get_viewfinder()
557 x = viewfinder.globalRect.x + viewfinder.width / 2.0
558- y = viewfinder.globalRect.y + viewfinder.height - 1.0
559+ y = viewfinder.globalRect.y + 1.0
560 self.pointing_device.move(x, y)
561 self.pointing_device.click()
562
563
564=== added directory 'tests/autopilot/camera_app/ubuntu_system_tests'
565=== added file 'tests/autopilot/camera_app/ubuntu_system_tests/__init__.py'
566--- tests/autopilot/camera_app/ubuntu_system_tests/__init__.py 1970-01-01 00:00:00 +0000
567+++ tests/autopilot/camera_app/ubuntu_system_tests/__init__.py 2016-04-21 10:43:56 +0000
568@@ -0,0 +1,19 @@
569+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
570+
571+#
572+# Ubuntu System Tests
573+# Copyright (C) 2015 Canonical
574+#
575+# This program is free software: you can redistribute it and/or modify
576+# it under the terms of the GNU General Public License as published by
577+# the Free Software Foundation, either version 3 of the License, or
578+# (at your option) any later version.
579+#
580+# This program is distributed in the hope that it will be useful,
581+# but WITHOUT ANY WARRANTY; without even the implied warranty of
582+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
583+# GNU General Public License for more details.
584+#
585+# You should have received a copy of the GNU General Public License
586+# along with this program. If not, see <http://www.gnu.org/licenses/>.
587+#
588
589=== added directory 'tests/autopilot/camera_app/ubuntu_system_tests/helpers'
590=== added file 'tests/autopilot/camera_app/ubuntu_system_tests/helpers/__init__.py'
591--- tests/autopilot/camera_app/ubuntu_system_tests/helpers/__init__.py 1970-01-01 00:00:00 +0000
592+++ tests/autopilot/camera_app/ubuntu_system_tests/helpers/__init__.py 2016-04-21 10:43:56 +0000
593@@ -0,0 +1,19 @@
594+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
595+
596+#
597+# Ubuntu System Tests
598+# Copyright (C) 2015 Canonical
599+#
600+# This program is free software: you can redistribute it and/or modify
601+# it under the terms of the GNU General Public License as published by
602+# the Free Software Foundation, either version 3 of the License, or
603+# (at your option) any later version.
604+#
605+# This program is distributed in the hope that it will be useful,
606+# but WITHOUT ANY WARRANTY; without even the implied warranty of
607+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
608+# GNU General Public License for more details.
609+#
610+# You should have received a copy of the GNU General Public License
611+# along with this program. If not, see <http://www.gnu.org/licenses/>.
612+#
613
614=== added file 'tests/autopilot/camera_app/ubuntu_system_tests/helpers/backup_restore_fixture.py'
615--- tests/autopilot/camera_app/ubuntu_system_tests/helpers/backup_restore_fixture.py 1970-01-01 00:00:00 +0000
616+++ tests/autopilot/camera_app/ubuntu_system_tests/helpers/backup_restore_fixture.py 2016-04-21 10:43:56 +0000
617@@ -0,0 +1,135 @@
618+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
619+#
620+# Ubuntu System Tests
621+# Copyright (C) 2015 Canonical
622+#
623+# This program is free software: you can redistribute it and/or modify
624+# it under the terms of the GNU General Public License as published by
625+# the Free Software Foundation, either version 3 of the License, or
626+# (at your option) any later version.
627+#
628+# This program is distributed in the hope that it will be useful,
629+# but WITHOUT ANY WARRANTY; without even the implied warranty of
630+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
631+# GNU General Public License for more details.
632+#
633+# You should have received a copy of the GNU General Public License
634+# along with this program. If not, see <http://www.gnu.org/licenses/>.
635+#
636+
637+import fixtures
638+import os
639+import shutil
640+import time
641+
642+from camera_app.ubuntu_system_tests.helpers import file_system as fs
643+from camera_app.ubuntu_system_tests.helpers import sqlite
644+
645+DEFAULT_STORE_ROOT = os.path.expanduser('~/.tmp_backup')
646+
647+
648+class BackupRestoreFixture(fixtures.Fixture):
649+ """Generic fixture class to backup and restore a specific directory."""
650+
651+ def __init__(self, backup_dir, storage_root_dir=DEFAULT_STORE_ROOT,
652+ start_clean=True):
653+ """
654+ :param backup_dir: Directory to be backed up.
655+ :param storage_root_dir: Root directoty to use as backup.
656+ :param start_clean: Whether to clean the backup directory.
657+ """
658+ if not os.path.exists(storage_root_dir):
659+ os.makedirs(storage_root_dir)
660+ self.backup_dir = backup_dir
661+ self.storage_dir = fixtures.TempDir(storage_root_dir)
662+ self.start_clean = start_clean
663+
664+ def setUp(self):
665+ """Backup the required directory and register restore actions."""
666+ super().setUp()
667+ self.useFixture(self.storage_dir)
668+ self.addCleanup(self._restore_directory)
669+ shutil.rmtree(self.storage_dir.path)
670+ if os.path.exists(self.backup_dir):
671+ if self.start_clean:
672+ fs.move_folder_contents(self.backup_dir, self.storage_dir.path)
673+ else:
674+ shutil.copytree(self.backup_dir, self.storage_dir.path)
675+
676+ def _restore_directory(self):
677+ """Move the backup data back to original location."""
678+ if os.path.exists(self.backup_dir):
679+ fs.clean_dir(self.backup_dir)
680+ if os.path.exists(self.storage_dir.path):
681+ fs.move_folder_contents(self.storage_dir.path, self.backup_dir)
682+
683+
684+class BackupRestoreRequestAccessFixture(fixtures.Fixture):
685+ """Fill the access requests for the an app by adding the answer in the
686+ its db and restoring the original data after the test execution."""
687+
688+ REQUESTS_SCHEMA = 'ApplicationId, Feature, Timestamp, Answer'
689+
690+ def __init__(self, db, app_id, feature, answer):
691+ """ Create a Fixture
692+ :param db: the path to the db
693+ :param app_id: the application id as it appears in the db
694+ :param feature: the feature as it appears in the db
695+ :param answer: True to allow access, False to deny access
696+ """
697+ self.db = db
698+ self.app_id = app_id
699+ self.feature = feature
700+ self.timestamp = int(str(time.time()).replace('.', ''))
701+ self.answer = int(answer)
702+
703+ def setUp(self):
704+ super().setUp()
705+
706+ # Get the initial request access state
707+ initial_state = self.get_access_request()
708+
709+ # Fill the db with the desired answer
710+ self.fill_access_request(self.app_id, self.feature, self.timestamp,
711+ self.answer)
712+
713+ # Restore the initial access state
714+ self.clean_request(initial_state)
715+
716+ def clean_request(self, state):
717+ """ Restore this request in case there was an initial state, otherwise
718+ delete the access request when there wasn't an initial state
719+ """
720+ if state:
721+ self.addCleanup(self.fill_access_request, *state)
722+ else:
723+ self.addCleanup(self.delete_access_request)
724+
725+ def fill_access_request(self, app_id, feature, timestamp, answer):
726+ """ Complete the db with the desired state to allow/deny the access
727+ to the app_id from the camera app
728+ """
729+ cmd = "INSERT OR REPLACE INTO requests (Id, {schema}) VALUES " \
730+ "((SELECT Id FROM requests WHERE ApplicationId = '{app_id}'), " \
731+ "'{app_id}', {feature}, {timestamp}, {answer});".\
732+ format(schema=self.REQUESTS_SCHEMA, app_id=app_id,
733+ feature=feature, timestamp=timestamp, answer=answer)
734+ sqlite.execute(self.db, cmd)
735+
736+ def delete_access_request(self):
737+ """ Delete the current state """
738+ cmd = "DELETE FROM requests WHERE ApplicationId = '{app_id}' AND " \
739+ "Feature = {feature}".format(app_id=self.app_id,
740+ feature=self.feature)
741+ sqlite.execute(self.db, cmd)
742+
743+ def get_access_request(self):
744+ """ Retrieve the first access request in the db for the current
745+ application id and feature
746+ :return: a list with the values following the table structure
747+ """
748+ cmd = "SELECT {schema} FROM requests WHERE ApplicationId = " \
749+ "'{app_id}' AND Feature = {feature}".\
750+ format(schema=self.REQUESTS_SCHEMA, app_id=self.app_id,
751+ feature=self.feature)
752+ return sqlite.query_one(self.db, cmd)
753
754=== added directory 'tests/autopilot/camera_app/ubuntu_system_tests/helpers/camera'
755=== added file 'tests/autopilot/camera_app/ubuntu_system_tests/helpers/camera/__init__.py'
756--- tests/autopilot/camera_app/ubuntu_system_tests/helpers/camera/__init__.py 1970-01-01 00:00:00 +0000
757+++ tests/autopilot/camera_app/ubuntu_system_tests/helpers/camera/__init__.py 2016-04-21 10:43:56 +0000
758@@ -0,0 +1,19 @@
759+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
760+
761+#
762+# Ubuntu System Tests
763+# Copyright (C) 2015 Canonical
764+#
765+# This program is free software: you can redistribute it and/or modify
766+# it under the terms of the GNU General Public License as published by
767+# the Free Software Foundation, either version 3 of the License, or
768+# (at your option) any later version.
769+#
770+# This program is distributed in the hope that it will be useful,
771+# but WITHOUT ANY WARRANTY; without even the implied warranty of
772+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
773+# GNU General Public License for more details.
774+#
775+# You should have received a copy of the GNU General Public License
776+# along with this program. If not, see <http://www.gnu.org/licenses/>.
777+#
778
779=== added file 'tests/autopilot/camera_app/ubuntu_system_tests/helpers/camera/fixture_setup.py'
780--- tests/autopilot/camera_app/ubuntu_system_tests/helpers/camera/fixture_setup.py 1970-01-01 00:00:00 +0000
781+++ tests/autopilot/camera_app/ubuntu_system_tests/helpers/camera/fixture_setup.py 2016-04-21 10:43:56 +0000
782@@ -0,0 +1,58 @@
783+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
784+
785+#
786+# Ubuntu System Tests
787+# Copyright (C) 2015 Canonical
788+#
789+# This program is free software: you can redistribute it and/or modify
790+# it under the terms of the GNU General Public License as published by
791+# the Free Software Foundation, either version 3 of the License, or
792+# (at your option) any later version.
793+#
794+# This program is distributed in the hope that it will be useful,
795+# but WITHOUT ANY WARRANTY; without even the implied warranty of
796+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
797+# GNU General Public License for more details.
798+#
799+# You should have received a copy of the GNU General Public License
800+# along with this program. If not, see <http://www.gnu.org/licenses/>.
801+#
802+
803+import fixtures
804+import os
805+
806+from camera_app.ubuntu_system_tests.helpers import file_system as fs
807+from camera_app.ubuntu_system_tests.helpers.backup_restore_fixture import (
808+ BackupRestoreRequestAccessFixture)
809+
810+SERVICE_DB = os.path.join(fs.DIR_HOME_LOCAL, 'share', 'CameraService',
811+ 'trust.db')
812+AUDIO_DB = os.path.join(fs.DIR_HOME_LOCAL, 'share', 'PulseAudio', 'trust.db')
813+
814+FEATURE = 0
815+APP_ID = 'com.ubuntu.camera_camera'
816+
817+
818+class SetCameraAccessRequests(fixtures.Fixture):
819+ """ Fill the camera and audio access request for the camera app and
820+ restore their initial state during the cleanup. """
821+
822+ def __init__(self, camera=True, audio=True):
823+ """ Init the fixture
824+ :param camera: Desired answer for the camera service
825+ :param audio: Desired answer for the audio service
826+ """
827+ self.camera = camera
828+ self.audio = audio
829+
830+ def setUp(self):
831+ super().setUp()
832+
833+ self.useFixture(BackupRestoreRequestAccessFixture(SERVICE_DB,
834+ APP_ID,
835+ FEATURE,
836+ self.camera))
837+ self.useFixture(BackupRestoreRequestAccessFixture(AUDIO_DB,
838+ APP_ID,
839+ FEATURE,
840+ self.audio))
841
842=== added file 'tests/autopilot/camera_app/ubuntu_system_tests/helpers/file_system.py'
843--- tests/autopilot/camera_app/ubuntu_system_tests/helpers/file_system.py 1970-01-01 00:00:00 +0000
844+++ tests/autopilot/camera_app/ubuntu_system_tests/helpers/file_system.py 2016-04-21 10:43:56 +0000
845@@ -0,0 +1,359 @@
846+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
847+
848+#
849+# Ubuntu System Tests
850+# Copyright (C) 2015 Canonical
851+#
852+# This program is free software: you can redistribute it and/or modify
853+# it under the terms of the GNU General Public License as published by
854+# the Free Software Foundation, either version 3 of the License, or
855+# (at your option) any later version.
856+#
857+# This program is distributed in the hope that it will be useful,
858+# but WITHOUT ANY WARRANTY; without even the implied warranty of
859+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
860+# GNU General Public License for more details.
861+#
862+# You should have received a copy of the GNU General Public License
863+# along with this program. If not, see <http://www.gnu.org/licenses/>.
864+#
865+
866+import filecmp
867+import getpass
868+import glob
869+import hashlib
870+import logging
871+import random
872+import os
873+import shutil
874+import string
875+import subprocess
876+
877+logger = logging.getLogger(__name__)
878+
879+FILE_PREFIX = 'file://'
880+IMAGE_PREFIX = 'image://'
881+MUSIC_PREFIX = 'music://'
882+ALBUM_PREFIX = 'album://'
883+
884+DIR_MUSIC = 'Music'
885+DIR_PICTURES = 'Pictures'
886+DIR_VIDEOS = 'Videos'
887+DIR_DOCUMENTS = 'Documents'
888+DIR_DOWNLOADS = 'Downloads'
889+DIR_CONFIG = '.config'
890+DIR_CACHE = '.cache'
891+DIR_LOCAL = '.local'
892+DIR_CAMERA_PICTURES = 'com.ubuntu.camera'
893+DIR_MUSIC_CONFIG = 'com.ubuntu.music'
894+DIR_GALLERY = 'com.ubuntu.gallery'
895+DIR_WEBBROWSER = 'webbrowser-app'
896+DIR_SHARE = 'share'
897+DIR_IMPORTED = 'imported'
898+
899+# Home dirs
900+DIR_HOME = os.path.expanduser('~')
901+DIR_HOME_MUSIC = os.path.join(DIR_HOME, DIR_MUSIC)
902+DIR_HOME_PICTURES = os.path.join(DIR_HOME, DIR_PICTURES)
903+DIR_HOME_VIDEOS = os.path.join(DIR_HOME, DIR_VIDEOS)
904+DIR_HOME_DOCUMENTS = os.path.join(DIR_HOME, DIR_DOCUMENTS)
905+DIR_HOME_DOWNLOADS = os.path.join(DIR_HOME, DIR_DOWNLOADS)
906+DIR_HOME_CONFIG = os.path.join(DIR_HOME, DIR_CONFIG)
907+DIR_HOME_LOCAL = os.path.join(DIR_HOME, DIR_LOCAL)
908+DIR_HOME_CACHE = os.path.join(DIR_HOME, DIR_CACHE)
909+
910+# .share dir
911+DIR_HOME_LOCAL_SHARE = os.path.join(DIR_HOME_LOCAL, DIR_SHARE)
912+
913+# Camera dirs
914+DIR_HOME_CAMERA_PICTURES = os.path.join(DIR_HOME_PICTURES,
915+ DIR_CAMERA_PICTURES)
916+DIR_HOME_CAMERA_VIDEOS = os.path.join(DIR_HOME_VIDEOS,
917+ DIR_CAMERA_PICTURES)
918+DIR_HOME_CAMERA_CONFIG = os.path.join(DIR_HOME_CONFIG,
919+ DIR_CAMERA_PICTURES)
920+DIR_HOME_CAMERA_THUMBNAILS = os.path.join(DIR_HOME, DIR_CACHE,
921+ DIR_CAMERA_PICTURES)
922+
923+# Gallery dirs
924+DIR_HOME_CACHE_GALLERY = os.path.join(DIR_HOME_CACHE, DIR_GALLERY)
925+
926+# Music dirs
927+DIR_HOME_MUSIC_CONFIG = os.path.join(DIR_HOME_CONFIG, DIR_MUSIC_CONFIG)
928+
929+# Pictures download
930+DIR_HOME_PICTURES_IMPORTED = os.path.join(DIR_HOME_PICTURES, DIR_IMPORTED)
931+
932+# Webbrowser dirs
933+DIR_HOME_LOCAL_SHARE_WEBBROWSER = os.path.join(DIR_HOME_LOCAL_SHARE,
934+ DIR_WEBBROWSER)
935+
936+# Test data dirs
937+DIR_TESTS = os.path.realpath(__file__ + '/../../tests')
938+DIR_TEST_DATA = os.path.join(DIR_TESTS, 'data')
939+DIR_TEST_DATA_AUDIO = os.path.join(DIR_TEST_DATA, 'audio')
940+DIR_TEST_DATA_HTML = os.path.join(DIR_TEST_DATA, 'html')
941+DIR_TEST_DATA_IMAGES = os.path.join(DIR_TEST_DATA, 'images')
942+DIR_TEST_DATA_SCOPES = os.path.join(DIR_TEST_DATA, 'scopes')
943+DIR_TEST_DATA_TEXT = os.path.join(DIR_TEST_DATA, 'text')
944+DIR_TEST_DATA_VIDEO = os.path.join(DIR_TEST_DATA, 'video')
945+DIR_TEST_DATA_DEVICES = os.path.join(DIR_TEST_DATA, 'devices')
946+DIR_TEST_DATA_DATABASES = os.path.join(DIR_TEST_DATA, 'databases')
947+
948+# Media dirs
949+DIR_MEDIA = '/media'
950+DIR_MEDIA_ROOT = os.path.join(DIR_MEDIA, getpass.getuser())
951+
952+# Misc dirs
953+DIR_TEMP = '/tmp'
954+
955+# Preinstalled click apps
956+DIR_PREINSTALLED_CLICK_APPS = '/usr/share/click/preinstalled/'
957+
958+
959+def delete_file(file_name):
960+ """Delete the file passed as parameter. In case the file does not
961+ exist, the deletion is skipped"""
962+ if os.path.isfile(file_name):
963+ os.unlink(file_name)
964+
965+
966+def create_directory_if_not_exists(directory):
967+ """Create the directory passed as parameter, if it does not
968+ already exist"""
969+ if not os.path.exists(directory):
970+ os.makedirs(directory)
971+
972+
973+def clean_files(dir_path):
974+ """Delete all the files in the directory.
975+ Subdirs are not deleted"""
976+ for root, dirs, files in os.walk(dir_path):
977+ for file in files:
978+ os.unlink(os.path.join(root, file))
979+
980+
981+def files_in_dir(dir_path):
982+ """Retrieve all the files in the directory"""
983+ f = []
984+ for (root, dirs, files) in os.walk(dir_path):
985+ f.extend(files)
986+ break
987+ return f
988+
989+
990+def recursive_files_in_dir(root):
991+ """Recursively search root dir and return list of absolute file paths."""
992+ file_list = []
993+ for dir, subdirs, files in os.walk(root):
994+ root_dir = os.path.join(root, dir)
995+ for file in files:
996+ file_list.append(os.path.abspath(os.path.join(root_dir, file)))
997+ return file_list
998+
999+
1000+def remove_dir(dir_path):
1001+ """Remove the directory"""
1002+ if os.path.isdir(dir_path):
1003+ shutil.rmtree(dir_path, True)
1004+
1005+
1006+def clean_dir(dir_path):
1007+ """Delete all the content of the directory"""
1008+ if os.path.isdir(dir_path):
1009+ for obj in os.listdir(dir_path):
1010+ obj_path = os.path.join(dir_path, obj)
1011+ if os.path.isfile(obj_path):
1012+ os.remove(obj_path)
1013+ else:
1014+ remove_dir(obj_path)
1015+
1016+
1017+def move_folder_contents(src_root, dst_root):
1018+ """
1019+ Move each top-level item individually from src folder to dest folder
1020+ leaving the top-level folder in place. Result will be an empty src folder
1021+ with all items moved to dest folder.
1022+
1023+ :param src_folder: The folder containing content to move
1024+ :parap dst_folder: The destination folder for all content
1025+
1026+ """
1027+ if os.path.isdir(src_root):
1028+ for item in os.listdir(src_root):
1029+ src = os.path.join(src_root, item)
1030+ dst = os.path.join(dst_root, item)
1031+ dst_folder = os.path.dirname(dst)
1032+ if not os.path.exists(dst_folder):
1033+ os.makedirs(dst_folder)
1034+ shutil.move(src, dst)
1035+
1036+
1037+def calculate_file_sha1(file_path):
1038+ """Calculates the sha1 digest for the file
1039+ :param file_path: The path to the file to calculate the sha1 digest
1040+ :return The sha1 digest for the file passed as parameter
1041+ :raise EnvironmentError: If the file couldn't be opened
1042+ """
1043+ sha = hashlib.sha1()
1044+ with open(file_path, 'rb') as f:
1045+ while True:
1046+ block = f.read(1024)
1047+ if not block:
1048+ break
1049+ sha.update(block)
1050+ return sha.hexdigest()
1051+
1052+
1053+def get_random_string(length=10):
1054+ """Get a string with random content"""
1055+ return ''.join(random.choice(string.ascii_uppercase) for i in
1056+ range(length))
1057+
1058+
1059+def compare_files(file_1, file_2):
1060+ """Return True if files match exactly, False otherwise."""
1061+ return filecmp.cmp(file_1, file_2)
1062+
1063+
1064+def _get_media_folders_from_root(root):
1065+ """Return a list of Music, Pictures or Video folders present at root."""
1066+ directories = []
1067+ for directory in [DIR_MUSIC, DIR_PICTURES, DIR_VIDEOS]:
1068+ sub_directory = os.path.join(root, directory)
1069+ if os.path.isdir(sub_directory):
1070+ directories.append(sub_directory)
1071+ return directories
1072+
1073+
1074+def is_media_folder_dir(media_dir):
1075+ """
1076+ Indicate if the media dir exists or not and checks its permissions
1077+ :param media_dir: The media dir required such as: Music, Videos, etc
1078+ :return: True if the media dir exists and False otherwise
1079+ """
1080+ try:
1081+ dir = get_media_folder_dir(media_dir)
1082+ return os.access(dir, os.R_OK) and os.access(dir, os.W_OK)
1083+ except RuntimeError:
1084+ return False
1085+
1086+
1087+def get_media_folder_dir(media_dir):
1088+ """Get the first media folders that matches with the media dir passed as
1089+ parameter.
1090+ :param media_dir: The media dir required such as: Music, Videos, etc
1091+ :return: The path to the media path
1092+ :raises: RuntimeError: when the media dir does not exist
1093+ """
1094+ if not os.path.isdir(DIR_MEDIA_ROOT):
1095+ raise RuntimeError('Media directory does not exist: ' + DIR_MEDIA_ROOT)
1096+
1097+ media_dirs = glob.glob(DIR_MEDIA_ROOT + '/*/{name}'.format(name=media_dir))
1098+ if not media_dirs:
1099+ raise RuntimeError('Media directory does not exist.')
1100+
1101+ return media_dirs[0]
1102+
1103+
1104+def _get_media_folders_for_home_folder():
1105+ """Get media folders from home folder."""
1106+ return _get_media_folders_from_root(DIR_HOME)
1107+
1108+
1109+def _get_media_folders_for_media_devices():
1110+ """Get media directories for any storage devices present."""
1111+ directories = []
1112+ if os.path.exists(DIR_MEDIA_ROOT):
1113+ for directory in os.listdir(DIR_MEDIA_ROOT):
1114+ media_directory = os.path.abspath(
1115+ os.path.join(DIR_MEDIA_ROOT, directory))
1116+ directories += _get_media_folders_from_root(media_directory)
1117+ return directories
1118+
1119+
1120+def get_media_folder_list():
1121+ """Return a list of media folder paths for current user."""
1122+ home_folders = _get_media_folders_for_home_folder()
1123+ media_folders = _get_media_folders_for_media_devices()
1124+ return home_folders + media_folders
1125+
1126+
1127+def create_random_file(path, ext='', length=10):
1128+ """ Create a file with random name and content in the specified dir
1129+ :param path: The path to create the file
1130+ :param ext: The extension for the file
1131+ :param length: the length of the file content
1132+ :return: The path of the created file
1133+ """
1134+ file_path = os.path.join(path, get_random_string() + ext)
1135+ if os.path.isfile(file_path):
1136+ raise RuntimeError('The file to be created already exists')
1137+
1138+ with open(file_path, 'w') as f:
1139+ f.write(get_random_string(length))
1140+
1141+ return file_path
1142+
1143+
1144+def get_file_path(path):
1145+ return FILE_PREFIX + path
1146+
1147+
1148+def remove_file_path(file_path):
1149+ return file_path.split(FILE_PREFIX)[1]
1150+
1151+
1152+def create_empty_file(file_path):
1153+ """ Create an empty file in case the file does not exist, otherwise skip
1154+ :param file_path: the path to the file
1155+ """
1156+ if not os.path.exists(file_path):
1157+ subprocess.call('touch {}'.format(file_path), shell=True)
1158+
1159+
1160+def _get_free_space(cmd, line):
1161+ """ Retrieve the free scpace in the disk measured in KB using df command
1162+ :param cmd: df command line
1163+ :param line: line in which the desired results are displayed
1164+ """
1165+ # df output is (device, size, used, available, percent, mountpoint)
1166+ return int(
1167+ subprocess.check_output(cmd, universal_newlines=True,
1168+ shell=True).split('\n')[line].split()[3])
1169+
1170+
1171+def get_disk_usage(path):
1172+ """ Retrieve the usage in KB used in a specific path, in case the path is
1173+ a dir, the result includes the subdirs
1174+ """
1175+ return int(subprocess.check_output('du -bs {}'.format(path),
1176+ universal_newlines=True,
1177+ shell=True).split()[0])
1178+
1179+
1180+def get_user_data_free_space():
1181+ """ Retrieve the free scpace in /userdata disk measured in KB
1182+ :return:
1183+ """
1184+ return _get_free_space('df /userdata', 1)
1185+
1186+
1187+def get_total_free_space():
1188+ """ Retrieve the free scpace in the disk measured in KB """
1189+ return _get_free_space('df --total', -2)
1190+
1191+
1192+def get_content_list_from_file_list(file_path_list, sort_content=False):
1193+ """Return a list of file contents from a list of file paths.
1194+ :param file_path_list: List of file paths to read.
1195+ :param sort_content: Whether to sort return list by content.
1196+ :return: List of file contents.
1197+ """
1198+ content_list = []
1199+ for file_path in file_path_list:
1200+ with open(file_path, 'rb') as f:
1201+ content_list.append(f.read())
1202+ if sort_content:
1203+ content_list = sorted(content_list)
1204+ return content_list
1205
1206=== added file 'tests/autopilot/camera_app/ubuntu_system_tests/helpers/sqlite.py'
1207--- tests/autopilot/camera_app/ubuntu_system_tests/helpers/sqlite.py 1970-01-01 00:00:00 +0000
1208+++ tests/autopilot/camera_app/ubuntu_system_tests/helpers/sqlite.py 2016-04-21 10:43:56 +0000
1209@@ -0,0 +1,62 @@
1210+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1211+#
1212+# Ubuntu System Tests
1213+# Copyright (C) 2015 Canonical
1214+#
1215+# This program is free software: you can redistribute it and/or modify
1216+# it under the terms of the GNU General Public License as published by
1217+# the Free Software Foundation, either version 3 of the License, or
1218+# (at your option) any later version.
1219+#
1220+# This program is distributed in the hope that it will be useful,
1221+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1222+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1223+# GNU General Public License for more details.
1224+#
1225+# You should have received a copy of the GNU General Public License
1226+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1227+#
1228+
1229+import sqlite3
1230+
1231+
1232+def open_db(db):
1233+ """ Open the sqlite db
1234+ :param db: The path to the DB
1235+ :return: the connection to the db
1236+ """
1237+ try:
1238+ conn = sqlite3.connect(db)
1239+ except sqlite3.Error as e:
1240+ e.args += 'DB {} cannot be opened'.format(db)
1241+ raise
1242+
1243+ return conn
1244+
1245+
1246+def query_one(db, command):
1247+ """ Query the db an retrieve one result
1248+ :param db: The path to the db
1249+ :param command: The query command
1250+ :return: a list with the query result
1251+ """
1252+ conn = open_db(db)
1253+
1254+ with conn:
1255+ cur = conn.cursor()
1256+ cur.execute(command)
1257+ return cur.fetchone()
1258+
1259+
1260+def execute(db, command, **kwargs):
1261+ """ Execute a command in the specified db
1262+ :param db: The path to the db
1263+ :param command: The command to execute
1264+ :param **kwargs: Keyword arguments for parameter substitution
1265+ """
1266+ conn = open_db(db)
1267+
1268+ with conn:
1269+ c = conn.cursor()
1270+ c.execute(command, kwargs)
1271+ conn.commit()

Subscribers

People subscribed via source and target branches