Merge lp:~osomon/webbrowser-app/limit-thumbnail-cache-size into lp:webbrowser-app
- limit-thumbnail-cache-size
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Günter Schwann | ||||
Approved revision: | 286 | ||||
Merged at revision: | 280 | ||||
Proposed branch: | lp:~osomon/webbrowser-app/limit-thumbnail-cache-size | ||||
Merge into: | lp:webbrowser-app | ||||
Diff against target: |
275 lines (+117/-22) 7 files modified
src/Ubuntu/Components/Extras/Browser/plugin.cpp (+18/-0) src/Ubuntu/Components/Extras/Browser/plugin.h (+8/-0) src/Ubuntu/Components/Extras/Browser/webthumbnail-utils.cpp (+66/-0) src/Ubuntu/Components/Extras/Browser/webthumbnail-utils.h (+19/-3) src/Ubuntu/Components/Extras/Browser/webview-thumbnailer.cpp (+6/-17) tests/unittests/history-domainlist-chronological-model/CMakeLists.txt (+0/-1) tests/unittests/history-domainlist-model/CMakeLists.txt (+0/-1) |
||||
To merge this branch: | bzr merge lp:~osomon/webbrowser-app/limit-thumbnail-cache-size | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Günter Schwann (community) | Approve | ||
PS Jenkins bot | continuous-integration | Approve | |
Review via email: mp+179247@code.launchpad.net |
Commit message
Delete old thumbnails to ensure the cache never grows bigger than 5MB.
Description of the change
PS Jenkins bot (ps-jenkins) wrote : | # |
- 283. By Olivier Tilloy
-
Remove unused dependencies.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:283
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Günter Schwann (schwann) wrote : | # |
45 + m_thumbnailUtil
I'd call m_thumbnailUtil
234 + QMetaObject:
You could add the connection type Qt::QueuedConne
148 + qint64 goal = MAX_CACHE_
So the max cache size is 90% of 5MB? Why?
- 284. By Olivier Tilloy
-
Ensure the thread terminates before deleting it.
- 285. By Olivier Tilloy
-
Make it (more) explicit that the method is called asynchronously.
- 286. By Olivier Tilloy
-
Do not expire cache entries if the total size doesn’t exceed the limit.
Olivier Tilloy (osomon) wrote : | # |
> 45 + m_thumbnailUtil
> I'd call m_thumbnailUtil
Done. Thanks for the suggestion.
> 234 + QMetaObject:
> "cacheThumbnail",
> You could add the connection type Qt::QueuedConne
> sure) that it's asynchronous.
Although not strictly necessary, done.
> 148 + qint64 goal = MAX_CACHE_
> So the max cache size is 90% of 5MB? Why?
That was a mistake on my part. This code is not triggered only if the total cache size exceeds the limit. The goal is 90% of the limit to have some buffer, so that we do not need to expire entries every time a new thumbnail is generated (this is inspired by the implementation of the QNetworkDiskCache class).
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:286
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Preview Diff
1 | === modified file 'src/Ubuntu/Components/Extras/Browser/plugin.cpp' | |||
2 | --- src/Ubuntu/Components/Extras/Browser/plugin.cpp 2013-07-30 17:26:49 +0000 | |||
3 | +++ src/Ubuntu/Components/Extras/Browser/plugin.cpp 2013-08-09 07:47:22 +0000 | |||
4 | @@ -26,11 +26,13 @@ | |||
5 | 26 | #include "tabs-model.h" | 26 | #include "tabs-model.h" |
6 | 27 | #include "bookmarks-model.h" | 27 | #include "bookmarks-model.h" |
7 | 28 | #include "webthumbnail-provider.h" | 28 | #include "webthumbnail-provider.h" |
8 | 29 | #include "webthumbnail-utils.h" | ||
9 | 29 | #include "webview-thumbnailer.h" | 30 | #include "webview-thumbnailer.h" |
10 | 30 | 31 | ||
11 | 31 | // Qt | 32 | // Qt |
12 | 32 | #include <QtCore/QDir> | 33 | #include <QtCore/QDir> |
13 | 33 | #include <QtCore/QStandardPaths> | 34 | #include <QtCore/QStandardPaths> |
14 | 35 | #include <QtCore/QThread> | ||
15 | 34 | #include <QtQml> | 36 | #include <QtQml> |
16 | 35 | 37 | ||
17 | 36 | void UbuntuBrowserPlugin::initializeEngine(QQmlEngine* engine, const char* uri) | 38 | void UbuntuBrowserPlugin::initializeEngine(QQmlEngine* engine, const char* uri) |
18 | @@ -43,9 +45,18 @@ | |||
19 | 43 | QQmlContext* context = engine->rootContext(); | 45 | QQmlContext* context = engine->rootContext(); |
20 | 44 | context->setContextProperty("dataLocation", dataLocation.absolutePath()); | 46 | context->setContextProperty("dataLocation", dataLocation.absolutePath()); |
21 | 45 | 47 | ||
22 | 48 | // This singleton lives in its own thread to ensure that | ||
23 | 49 | // disk I/O is not performed in the UI thread. | ||
24 | 50 | WebThumbnailUtils& utils = WebThumbnailUtils::instance(); | ||
25 | 51 | m_thumbnailUtilsThread = new QThread; | ||
26 | 52 | utils.moveToThread(m_thumbnailUtilsThread); | ||
27 | 53 | m_thumbnailUtilsThread->start(); | ||
28 | 54 | |||
29 | 46 | WebThumbnailProvider* thumbnailer = new WebThumbnailProvider; | 55 | WebThumbnailProvider* thumbnailer = new WebThumbnailProvider; |
30 | 47 | engine->addImageProvider(QLatin1String("webthumbnail"), thumbnailer); | 56 | engine->addImageProvider(QLatin1String("webthumbnail"), thumbnailer); |
31 | 48 | context->setContextProperty("WebThumbnailer", thumbnailer); | 57 | context->setContextProperty("WebThumbnailer", thumbnailer); |
32 | 58 | |||
33 | 59 | connect(engine, SIGNAL(destroyed()), SLOT(onEngineDestroyed())); | ||
34 | 49 | } | 60 | } |
35 | 50 | 61 | ||
36 | 51 | void UbuntuBrowserPlugin::registerTypes(const char* uri) | 62 | void UbuntuBrowserPlugin::registerTypes(const char* uri) |
37 | @@ -61,3 +72,10 @@ | |||
38 | 61 | qmlRegisterType<BookmarksModel>(uri, 0, 1, "BookmarksModel"); | 72 | qmlRegisterType<BookmarksModel>(uri, 0, 1, "BookmarksModel"); |
39 | 62 | qmlRegisterType<WebviewThumbnailer>(uri, 0, 1, "WebviewThumbnailer"); | 73 | qmlRegisterType<WebviewThumbnailer>(uri, 0, 1, "WebviewThumbnailer"); |
40 | 63 | } | 74 | } |
41 | 75 | |||
42 | 76 | void UbuntuBrowserPlugin::onEngineDestroyed() | ||
43 | 77 | { | ||
44 | 78 | m_thumbnailUtilsThread->quit(); | ||
45 | 79 | m_thumbnailUtilsThread->wait(); | ||
46 | 80 | delete m_thumbnailUtilsThread; | ||
47 | 81 | } | ||
48 | 64 | 82 | ||
49 | === modified file 'src/Ubuntu/Components/Extras/Browser/plugin.h' | |||
50 | --- src/Ubuntu/Components/Extras/Browser/plugin.h 2013-06-11 12:06:15 +0000 | |||
51 | +++ src/Ubuntu/Components/Extras/Browser/plugin.h 2013-08-09 07:47:22 +0000 | |||
52 | @@ -22,6 +22,8 @@ | |||
53 | 22 | // Qt | 22 | // Qt |
54 | 23 | #include <QtQml/QQmlExtensionPlugin> | 23 | #include <QtQml/QQmlExtensionPlugin> |
55 | 24 | 24 | ||
56 | 25 | class QThread; | ||
57 | 26 | |||
58 | 25 | class UbuntuBrowserPlugin : public QQmlExtensionPlugin | 27 | class UbuntuBrowserPlugin : public QQmlExtensionPlugin |
59 | 26 | { | 28 | { |
60 | 27 | Q_OBJECT | 29 | Q_OBJECT |
61 | @@ -30,6 +32,12 @@ | |||
62 | 30 | public: | 32 | public: |
63 | 31 | void initializeEngine(QQmlEngine* engine, const char* uri); | 33 | void initializeEngine(QQmlEngine* engine, const char* uri); |
64 | 32 | void registerTypes(const char* uri); | 34 | void registerTypes(const char* uri); |
65 | 35 | |||
66 | 36 | private: | ||
67 | 37 | QThread* m_thumbnailUtilsThread; | ||
68 | 38 | |||
69 | 39 | private Q_SLOTS: | ||
70 | 40 | void onEngineDestroyed(); | ||
71 | 33 | }; | 41 | }; |
72 | 34 | 42 | ||
73 | 35 | #endif // __PLUGIN_H__ | 43 | #endif // __PLUGIN_H__ |
74 | 36 | 44 | ||
75 | === modified file 'src/Ubuntu/Components/Extras/Browser/webthumbnail-utils.cpp' | |||
76 | --- src/Ubuntu/Components/Extras/Browser/webthumbnail-utils.cpp 2013-07-10 12:02:00 +0000 | |||
77 | +++ src/Ubuntu/Components/Extras/Browser/webthumbnail-utils.cpp 2013-08-09 07:47:22 +0000 | |||
78 | @@ -16,11 +16,32 @@ | |||
79 | 16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | 16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
80 | 17 | */ | 17 | */ |
81 | 18 | 18 | ||
82 | 19 | #include "domain-utils.h" | ||
83 | 19 | #include "webthumbnail-utils.h" | 20 | #include "webthumbnail-utils.h" |
84 | 20 | 21 | ||
85 | 21 | // Qt | 22 | // Qt |
86 | 22 | #include <QtCore/QCryptographicHash> | 23 | #include <QtCore/QCryptographicHash> |
87 | 24 | #include <QtCore/QFile> | ||
88 | 23 | #include <QtCore/QStandardPaths> | 25 | #include <QtCore/QStandardPaths> |
89 | 26 | #include <QtCore/QUrl> | ||
90 | 27 | #include <QtGui/QImage> | ||
91 | 28 | |||
92 | 29 | #define MAX_CACHE_SIZE_IN_BYTES 5 * 1024 * 1024 // 5MB | ||
93 | 30 | |||
94 | 31 | WebThumbnailUtils::WebThumbnailUtils(QObject* parent) | ||
95 | 32 | : QObject(parent) | ||
96 | 33 | { | ||
97 | 34 | } | ||
98 | 35 | |||
99 | 36 | WebThumbnailUtils& WebThumbnailUtils::instance() | ||
100 | 37 | { | ||
101 | 38 | static WebThumbnailUtils utils; | ||
102 | 39 | return utils; | ||
103 | 40 | } | ||
104 | 41 | |||
105 | 42 | WebThumbnailUtils::~WebThumbnailUtils() | ||
106 | 43 | { | ||
107 | 44 | } | ||
108 | 24 | 45 | ||
109 | 25 | QDir WebThumbnailUtils::cacheLocation() | 46 | QDir WebThumbnailUtils::cacheLocation() |
110 | 26 | { | 47 | { |
111 | @@ -40,3 +61,48 @@ | |||
112 | 40 | QString hash(QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Md5).toHex()); | 61 | QString hash(QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Md5).toHex()); |
113 | 41 | return cacheLocation().absoluteFilePath(hash + ".png"); | 62 | return cacheLocation().absoluteFilePath(hash + ".png"); |
114 | 42 | } | 63 | } |
115 | 64 | |||
116 | 65 | void WebThumbnailUtils::cacheThumbnail(const QUrl& url, const QImage& thumbnail) const | ||
117 | 66 | { | ||
118 | 67 | ensureCacheLocation(); | ||
119 | 68 | QFileInfo file = thumbnailFile(url); | ||
120 | 69 | bool saved = thumbnail.save(file.absoluteFilePath()); | ||
121 | 70 | |||
122 | 71 | if (saved) { | ||
123 | 72 | // Make a link to the thumbnail file for the corresponding domain’s thumbnail. | ||
124 | 73 | QUrl domain(DomainUtils::extractTopLevelDomainName(url)); | ||
125 | 74 | QString domainThumbnail = WebThumbnailUtils::thumbnailFile(domain).absoluteFilePath(); | ||
126 | 75 | if (QFile::exists(domainThumbnail)) { | ||
127 | 76 | QFile::remove(domainThumbnail); | ||
128 | 77 | } | ||
129 | 78 | QFile::link(file.fileName(), domainThumbnail); | ||
130 | 79 | } | ||
131 | 80 | |||
132 | 81 | expireCache(); | ||
133 | 82 | } | ||
134 | 83 | |||
135 | 84 | void WebThumbnailUtils::expireCache() const | ||
136 | 85 | { | ||
137 | 86 | QDir cache = cacheLocation(); | ||
138 | 87 | if (!cache.exists()) { | ||
139 | 88 | return; | ||
140 | 89 | } | ||
141 | 90 | QStringList nameFilters = QStringList() << "*.png"; | ||
142 | 91 | QDir::Filters filters = QDir::Files | QDir::NoDotAndDotDot; | ||
143 | 92 | QDir::SortFlags sort = QDir::Time; | ||
144 | 93 | QFileInfoList entries = cache.entryInfoList(nameFilters, filters, sort); | ||
145 | 94 | qint64 currentSize = 0; | ||
146 | 95 | Q_FOREACH(const QFileInfo& entry, entries) { | ||
147 | 96 | currentSize += entry.size(); | ||
148 | 97 | } | ||
149 | 98 | if (currentSize > MAX_CACHE_SIZE_IN_BYTES) { | ||
150 | 99 | qint64 goal = MAX_CACHE_SIZE_IN_BYTES * 9 / 10; | ||
151 | 100 | while (!entries.isEmpty() && (currentSize > goal)) { | ||
152 | 101 | QFileInfo entry = entries.takeLast(); | ||
153 | 102 | qint64 size = entry.size(); | ||
154 | 103 | if (QFile::remove(entry.absoluteFilePath())) { | ||
155 | 104 | currentSize -= size; | ||
156 | 105 | } | ||
157 | 106 | } | ||
158 | 107 | } | ||
159 | 108 | } | ||
160 | 43 | 109 | ||
161 | === modified file 'src/Ubuntu/Components/Extras/Browser/webthumbnail-utils.h' | |||
162 | --- src/Ubuntu/Components/Extras/Browser/webthumbnail-utils.h 2013-07-10 12:02:00 +0000 | |||
163 | +++ src/Ubuntu/Components/Extras/Browser/webthumbnail-utils.h 2013-08-09 07:47:22 +0000 | |||
164 | @@ -22,14 +22,30 @@ | |||
165 | 22 | // Qt | 22 | // Qt |
166 | 23 | #include <QtCore/QDir> | 23 | #include <QtCore/QDir> |
167 | 24 | #include <QtCore/QFileInfo> | 24 | #include <QtCore/QFileInfo> |
171 | 25 | #include <QtCore/QUrl> | 25 | #include <QtCore/QObject> |
172 | 26 | 26 | ||
173 | 27 | class WebThumbnailUtils | 27 | class QImage; |
174 | 28 | class QUrl; | ||
175 | 29 | |||
176 | 30 | class WebThumbnailUtils : public QObject | ||
177 | 28 | { | 31 | { |
178 | 32 | Q_OBJECT | ||
179 | 33 | |||
180 | 29 | public: | 34 | public: |
181 | 35 | static WebThumbnailUtils& instance(); | ||
182 | 36 | ~WebThumbnailUtils(); | ||
183 | 37 | |||
184 | 30 | static QDir cacheLocation(); | 38 | static QDir cacheLocation(); |
185 | 31 | static void ensureCacheLocation(); | 39 | static void ensureCacheLocation(); |
186 | 32 | static QFileInfo thumbnailFile(const QUrl& url); | 40 | static QFileInfo thumbnailFile(const QUrl& url); |
187 | 41 | |||
188 | 42 | public Q_SLOTS: | ||
189 | 43 | void cacheThumbnail(const QUrl& url, const QImage& thumbnail) const; | ||
190 | 44 | |||
191 | 45 | private: | ||
192 | 46 | WebThumbnailUtils(QObject* parent=0); | ||
193 | 47 | |||
194 | 48 | void expireCache() const; | ||
195 | 33 | }; | 49 | }; |
196 | 34 | 50 | ||
197 | 35 | #endif // __WEBTHUMBNAIL_UTILS_H__ | 51 | #endif // __WEBTHUMBNAIL_UTILS_H__ |
198 | 36 | 52 | ||
199 | === modified file 'src/Ubuntu/Components/Extras/Browser/webview-thumbnailer.cpp' | |||
200 | --- src/Ubuntu/Components/Extras/Browser/webview-thumbnailer.cpp 2013-08-06 10:16:28 +0000 | |||
201 | +++ src/Ubuntu/Components/Extras/Browser/webview-thumbnailer.cpp 2013-08-09 07:47:22 +0000 | |||
202 | @@ -16,12 +16,11 @@ | |||
203 | 16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | 16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
204 | 17 | */ | 17 | */ |
205 | 18 | 18 | ||
206 | 19 | #include "domain-utils.h" | ||
207 | 20 | #include "webthumbnail-utils.h" | 19 | #include "webthumbnail-utils.h" |
208 | 21 | #include "webview-thumbnailer.h" | 20 | #include "webview-thumbnailer.h" |
209 | 22 | 21 | ||
210 | 23 | // Qt | 22 | // Qt |
212 | 24 | #include <QtCore/QFile> | 23 | #include <QtCore/QMetaObject> |
213 | 25 | #include <QtCore/QTimer> | 24 | #include <QtCore/QTimer> |
214 | 26 | #include <QtQuick/private/qsgrenderer_p.h> | 25 | #include <QtQuick/private/qsgrenderer_p.h> |
215 | 27 | #include <QtWebKit/private/qquickwebpage_p.h> | 26 | #include <QtWebKit/private/qquickwebpage_p.h> |
216 | @@ -140,20 +139,12 @@ | |||
217 | 140 | 139 | ||
218 | 141 | fbo.release(); | 140 | fbo.release(); |
219 | 142 | 141 | ||
220 | 142 | const QUrl& url = m_webview->url(); | ||
221 | 143 | QImage image = fbo.toImage().scaled(m_targetSize, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); | 143 | QImage image = fbo.toImage().scaled(m_targetSize, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); |
222 | 144 | 144 | ||
235 | 145 | WebThumbnailUtils::ensureCacheLocation(); | 145 | // Invoke the method asynchronously. |
236 | 146 | QUrl url = m_webview->url(); | 146 | QMetaObject::invokeMethod(&WebThumbnailUtils::instance(), "cacheThumbnail", |
237 | 147 | QFileInfo thumbnail = WebThumbnailUtils::thumbnailFile(url); | 147 | Qt::QueuedConnection, Q_ARG(QUrl, url), Q_ARG(QImage, image)); |
226 | 148 | bool saved = image.save(thumbnail.absoluteFilePath()); | ||
227 | 149 | |||
228 | 150 | // Make a link to the thumbnail file for the corresponding domain’s thumbnail. | ||
229 | 151 | QUrl domain(DomainUtils::extractTopLevelDomainName(url)); | ||
230 | 152 | QString domainThumbnail = WebThumbnailUtils::thumbnailFile(domain).absoluteFilePath(); | ||
231 | 153 | if (QFile::exists(domainThumbnail)) { | ||
232 | 154 | QFile::remove(domainThumbnail); | ||
233 | 155 | } | ||
234 | 156 | QFile::link(thumbnail.fileName(), domainThumbnail); | ||
238 | 157 | 148 | ||
239 | 158 | root.removeChildNode(node); | 149 | root.removeChildNode(node); |
240 | 159 | 150 | ||
241 | @@ -165,9 +156,7 @@ | |||
242 | 165 | } | 156 | } |
243 | 166 | } | 157 | } |
244 | 167 | 158 | ||
248 | 168 | if (saved) { | 159 | Q_EMIT thumbnailRendered(url); |
246 | 169 | Q_EMIT thumbnailRendered(url); | ||
247 | 170 | } | ||
249 | 171 | 160 | ||
250 | 172 | return oldNode; | 161 | return oldNode; |
251 | 173 | } | 162 | } |
252 | 174 | 163 | ||
253 | === modified file 'tests/unittests/history-domainlist-chronological-model/CMakeLists.txt' | |||
254 | --- tests/unittests/history-domainlist-chronological-model/CMakeLists.txt 2013-07-29 10:32:13 +0000 | |||
255 | +++ tests/unittests/history-domainlist-chronological-model/CMakeLists.txt 2013-08-09 07:47:22 +0000 | |||
256 | @@ -6,7 +6,6 @@ | |||
257 | 6 | ${webbrowser-plugin_SOURCE_DIR}/history-domainlist-chronological-model.cpp | 6 | ${webbrowser-plugin_SOURCE_DIR}/history-domainlist-chronological-model.cpp |
258 | 7 | ${webbrowser-plugin_SOURCE_DIR}/history-model.cpp | 7 | ${webbrowser-plugin_SOURCE_DIR}/history-model.cpp |
259 | 8 | ${webbrowser-plugin_SOURCE_DIR}/history-timeframe-model.cpp | 8 | ${webbrowser-plugin_SOURCE_DIR}/history-timeframe-model.cpp |
260 | 9 | ${webbrowser-plugin_SOURCE_DIR}/webthumbnail-utils.cpp | ||
261 | 10 | tst_HistoryDomainListChronologicalModelTests.cpp | 9 | tst_HistoryDomainListChronologicalModelTests.cpp |
262 | 11 | ) | 10 | ) |
263 | 12 | add_executable(${TEST} ${SOURCES}) | 11 | add_executable(${TEST} ${SOURCES}) |
264 | 13 | 12 | ||
265 | === modified file 'tests/unittests/history-domainlist-model/CMakeLists.txt' | |||
266 | --- tests/unittests/history-domainlist-model/CMakeLists.txt 2013-07-29 10:32:13 +0000 | |||
267 | +++ tests/unittests/history-domainlist-model/CMakeLists.txt 2013-08-09 07:47:22 +0000 | |||
268 | @@ -5,7 +5,6 @@ | |||
269 | 5 | ${webbrowser-plugin_SOURCE_DIR}/history-domainlist-model.cpp | 5 | ${webbrowser-plugin_SOURCE_DIR}/history-domainlist-model.cpp |
270 | 6 | ${webbrowser-plugin_SOURCE_DIR}/history-model.cpp | 6 | ${webbrowser-plugin_SOURCE_DIR}/history-model.cpp |
271 | 7 | ${webbrowser-plugin_SOURCE_DIR}/history-timeframe-model.cpp | 7 | ${webbrowser-plugin_SOURCE_DIR}/history-timeframe-model.cpp |
272 | 8 | ${webbrowser-plugin_SOURCE_DIR}/webthumbnail-utils.cpp | ||
273 | 9 | tst_HistoryDomainListModelTests.cpp | 8 | tst_HistoryDomainListModelTests.cpp |
274 | 10 | ) | 9 | ) |
275 | 11 | add_executable(${TEST} ${SOURCES}) | 10 | add_executable(${TEST} ${SOURCES}) |
FAILED: Continuous integration, rev:282 jenkins. qa.ubuntu. com/job/ webbrowser- app-ci/ 276/ jenkins. qa.ubuntu. com/job/ generic- mediumtests- saucy/2032/ console jenkins. qa.ubuntu. com/job/ webbrowser- app-saucy- amd64-ci/ 159/console jenkins. qa.ubuntu. com/job/ webbrowser- app-saucy- armhf-ci/ 159/console jenkins. qa.ubuntu. com/job/ webbrowser- app-saucy- i386-ci/ 159/console jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- saucy/2037/ console
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins: 8080/job/ webbrowser- app-ci/ 276/rebuild
http://