Merge lp:camera-app/staging into lp:camera-app
- staging
- Merge into trunk
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 | ||||||||||||||||||||||||||||||||
Related bugs: |
|
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 AdvancedCameraS
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 AdvancedCameraS
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() |