Merge lp:~stolowski/unity-scope-home/phone-disable-filters into lp:unity-scope-home

Proposed by Paweł Stołowski
Status: Merged
Approved by: Francis Ginther
Approved revision: 183
Merged at revision: 174
Proposed branch: lp:~stolowski/unity-scope-home/phone-disable-filters
Merge into: lp:unity-scope-home
Diff against target: 954 lines (+519/-154)
13 files modified
configure.ac (+1/-1)
src/platform-info.vala (+14/-11)
src/scope-registry.vala (+2/-1)
src/scope.vala (+18/-6)
src/search-util.vala (+0/-11)
tests/fake-server/samples/remote-scopes-minimal.txt (+1/-0)
tests/unit/Makefile.am (+14/-3)
tests/unit/data/unity/scopes/masterscope_a.scope (+2/-2)
tests/unit/data/unity/scopes/masterscope_b.scope (+2/-2)
tests/unit/data/unity/scopes/more_suggestions.scope (+17/-0)
tests/unit/data/unity/scopes/reference.scope (+33/-0)
tests/unit/test-home-scope.vala (+221/-117)
tests/unit/test-utils.vala (+194/-0)
To merge this branch: bzr merge lp:~stolowski/unity-scope-home/phone-disable-filters
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Michal Hruby (community) Approve
Review via email: mp+187021@code.launchpad.net

Commit message

Disable filter updates if phone factor is not 'desktop'.

Description of the change

Disable filter updates if phone factor is not 'desktop' (i.e. currently displayed categores are not reflected in the filters of Home view). This is required because UI in Unity 8 support single selection filters only (any selection made by the user will narrow results to single category in Home).

In addition, sort scope registry by scope id to give more sensible default order on the phone, until real category sorting works there.

To post a comment you must log in.
Revision history for this message
Michal Hruby (mhr3) wrote :

Could we see some tests for this? Not updating the filters seems a bit scary, does changing the query still reset them etc? Those things should be ensured by a test.

review: Needs Information
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Paweł Stołowski (stolowski) wrote :

> Could we see some tests for this? Not updating the filters seems a bit scary,
> does changing the query still reset them etc? Those things should be ensured
> by a test.

Right, that was a bug; fixed.

As much as I'd love to add a test for it, I don't see a way to do it with a reasonable amount of effort due to the number of prerequisites or mocks needed.

The point of this change is to disable updates of filters in the UI from the backend (which only Home Scope does). The logic that sets scopes to query from SSS recommendations, always-search scopes or default view stays.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michal Hruby (mhr3) wrote :

222 + echo $(nodist_test_home_scope_SOURCES)

Would be nice to get rid of it :)

418 + Process.close_pid (server_pid);

I think this frees the handle on some systems, yet it's used later in stop(), fortunately it's a noop on linux, anyway, better remove it (or move to destructor to have super-clean code).

519 + var proxy = acquire_test_proxy (HOME_SCOPE_DBUS_NAME, HOME_SCOPE_DBUS_PATH);
520 + var channel_id = open_channel (proxy, ChannelType.GLOBAL, null);
521 +
522 + assert (channel_id != null);

Looks like something that should go into setup() + close_channel() in teardown().

80 + string? form_factor = scope_search.search_context.search_metadata.form_factor;

Micro-optimisation - turn this into unowned string.

I know this was a hard fight, but it really opens doors to have more thorough testing of the entire home-scope codebase... Well done! :)

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michal Hruby (mhr3) wrote :

+1

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Francis Ginther (fginther) wrote :

Autolanding failure caused by catching the archive in the middle of an update (hash sum mismatch). Re-approved.

Revision history for this message
PS Jenkins bot (ps-jenkins) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'configure.ac'
2--- configure.ac 2013-09-27 14:33:11 +0000
3+++ configure.ac 2013-10-01 10:06:09 +0000
4@@ -104,7 +104,7 @@
5 ####################################################################
6 AC_ARG_ENABLE([headless-tests],
7 AS_HELP_STRING([--enable-headless-tests=@<:@no/yes@:>@],[enable headless test suite (requires Xvfb) @<:@default=no@:>@]),,
8- [enable_headless_tests=no])
9+ [enable_headless_tests=yes])
10
11 AM_CONDITIONAL([ENABLE_HEADLESS_TESTS],[test "x$enable_headless_tests" != "xno"])
12
13
14=== modified file 'src/platform-info.vala'
15--- src/platform-info.vala 2013-08-28 14:24:07 +0000
16+++ src/platform-info.vala 2013-10-01 10:06:09 +0000
17@@ -92,17 +92,20 @@
18 try
19 {
20 is_ready = false;
21- var connection = yield Bus.get (BusType.SYSTEM, null);
22- var reply = yield connection.call ("org.ofono", "/ril_0",
23- "org.ofono.SimManager",
24- "GetProperties", null,
25- new VariantType ("(a{sv})"),
26- DBusCallFlags.NONE, -1, null);
27- reply = reply.get_child_value (0);
28- var mcc_v = reply.lookup_value ("MobileCountryCode", VariantType.STRING);
29- var mnc_v = reply.lookup_value ("MobileNetworkCode", VariantType.STRING);
30- if (mcc_v != null) country_code = mcc_v.get_string ();
31- if (mnc_v != null) network_code = mnc_v.get_string ();
32+ if (Environment.get_variable ("HOME_SCOPE_IGNORE_OFONO") == null)
33+ {
34+ var connection = yield Bus.get (BusType.SYSTEM, null);
35+ var reply = yield connection.call ("org.ofono", "/ril_0",
36+ "org.ofono.SimManager",
37+ "GetProperties", null,
38+ new VariantType ("(a{sv})"),
39+ DBusCallFlags.NONE, -1, null);
40+ reply = reply.get_child_value (0);
41+ var mcc_v = reply.lookup_value ("MobileCountryCode", VariantType.STRING);
42+ var mnc_v = reply.lookup_value ("MobileNetworkCode", VariantType.STRING);
43+ if (mcc_v != null) country_code = mcc_v.get_string ();
44+ if (mnc_v != null) network_code = mnc_v.get_string ();
45+ }
46 }
47 catch (Error err)
48 {
49
50=== modified file 'src/scope-registry.vala'
51--- src/scope-registry.vala 2013-09-04 10:28:06 +0000
52+++ src/scope-registry.vala 2013-10-01 10:06:09 +0000
53@@ -119,7 +119,8 @@
54 // with remote scopes; we would need to wait for remote-scopes query to finish to know if
55 // if this master is needed for remote results.
56
57- this.registry.append (node);
58+ // sort master scopes by scope_id
59+ this.registry.insert_sorted (node, (node_a, node_b) => { return strcmp (node_a.scope_info.id, node_b.scope_info.id); });
60 master_to_node.insert (node.scope_info.id, node);
61 }
62 else
63
64=== modified file 'src/scope.vala'
65--- src/scope.vala 2013-09-20 16:40:39 +0000
66+++ src/scope.vala 2013-10-01 10:06:09 +0000
67@@ -42,7 +42,7 @@
68 private FilterState filter_state = new FilterState ();
69 private KeywordSearch keywords_search = new KeywordSearch ();
70 private bool smart_scopes_initialized = false;
71- private bool smart_scopes_ready = false;
72+ public bool smart_scopes_ready { get; internal set; default = false; }
73 private SmartScopes.SmartScopeClientInterface sss_client = null;
74 private SmartScopes.ChannelIdMap channel_id_map = new SmartScopes.ChannelIdMap ();
75 private uint metrics_timer;
76@@ -616,6 +616,13 @@
77 uint num_scopes = 0;
78
79 bool flushing_enabled = false;
80+ unowned string? form_factor = scope_search.search_context.search_metadata.form_factor;
81+ if (form_factor == null)
82+ form_factor = "unknown"; // set to 'unknown' as it's sent with smart scopes request
83+ bool disable_filter_updates = (form_factor != "desktop");
84+
85+ if (disable_filter_updates)
86+ debug ("Filter updates disabled, form factor is %s", form_factor);
87
88 // ids of scopes recommended by Smart Scope Service
89 var recommended_search_scopes = new List<SmartScopes.RecommendedScope?> ();
90@@ -711,6 +718,8 @@
91 unowned Unity.OptionsFilter categories_filter = scope_search.get_filter ("categories") as Unity.OptionsFilter;
92 unowned Unity.OptionsFilter sources_filter = scope_search.get_filter ("sources") as Unity.OptionsFilter;
93
94+ bool relations_changed = false;
95+
96 // if query is empty but it's not just a filter change (i.e. state is REMOVES_FROM_PREVIOUS_QUERY or NEW_QUERY),
97 // then apply default user filters.
98 if (empty_query && search_query_changed != SearchQueryChange.NOT_CHANGED)
99@@ -724,8 +733,12 @@
100 default_view = true;
101 }
102
103- debug ("Updating filter interrelationships");
104- bool relations_changed = filter_state.update_filter_relations (scope_search.channel_id, categories_filter, sources_filter);
105+ if (!disable_filter_updates) {
106+ debug ("Updating filter interrelationships");
107+ relations_changed = filter_state.update_filter_relations (scope_search.channel_id, categories_filter, sources_filter);
108+ } else {
109+ needs_filter_update = false; //reset filter update flag if on the phone
110+ }
111
112 // caution: push_filter_settings may get cancelled if we ever yield before;
113 // in such case internal filter state must be updated later (at the end of search).
114@@ -888,7 +901,6 @@
115 }
116
117 sss_query_started = true;
118- var form_factor = SearchUtil.get_form_factor (scope_search);
119 sss_client.search.begin (search_string, form_factor, session_id, remote_scopes_to_query, scope_mgr.disabled_scopes,
120 (scope_id, row) =>
121 {
122@@ -1024,7 +1036,7 @@
123 use_recommended_scopes = true;
124
125 // only update filters with searched scopes when not in the default view, otherwise default view filters may get de-selected
126- if (!default_view)
127+ if (!default_view && !disable_filter_updates)
128 {
129 debug ("Updating filter state");
130 needs_filter_update |= SearchUtil.update_filters (search_scopes, use_recommended_scopes ? recommended_search_scopes : null,
131@@ -1108,7 +1120,7 @@
132 }
133
134 // update filter state again, but this time check result counts and send the update only if any of the highlighted masters has no results
135- if (!default_view)
136+ if (!default_view && !disable_filter_updates)
137 needs_filter_update |= SearchUtil.update_filters (search_scopes, use_recommended_scopes ? recommended_search_scopes : null, scope_search, filter_change_only == false);
138
139 if (needs_filter_update)
140
141=== modified file 'src/search-util.vala'
142--- src/search-util.vala 2013-06-14 15:49:21 +0000
143+++ src/search-util.vala 2013-10-01 10:06:09 +0000
144@@ -317,15 +317,4 @@
145 }
146 return found;
147 }
148-
149- public string get_form_factor (Unity.AggregatedScopeSearch scope_search)
150- {
151- const string FORM_FACTOR_HINT = "form-factor";
152- unowned Variant? hint = scope_search.hints[FORM_FACTOR_HINT];
153- if (hint != null && hint.is_of_type (VariantType.STRING))
154- {
155- return hint.get_string ();
156- }
157- return "unknown";
158- }
159 }
160
161=== added file 'tests/fake-server/samples/remote-scopes-minimal.txt'
162--- tests/fake-server/samples/remote-scopes-minimal.txt 1970-01-01 00:00:00 +0000
163+++ tests/fake-server/samples/remote-scopes-minimal.txt 2013-10-01 10:06:09 +0000
164@@ -0,0 +1,1 @@
165+[{"description": "This is an Ubuntu search plugin that enables information from Ubuntu One Music Store to be searched and displayed in the Dash underneath the More Suggestions header. If you do not wish to search this content source, you can disable this search plugin.\n", "screenshot": "http://ubuntuone.com/7XpGFTkSugUON0PEsBBYHK", "name": "Ubuntu One Music Search", "keywords": ["u1ms", "u1", "ubuntuone"], "id": "more_suggestions-u1ms.scope", "icon": "file:///usr/share/icons/unity-icon-theme/places/svg/service-u1.svg"}, {"description": "This is an Ubuntu search plugin that enables information from Skimlinks to be searched and displayed in the Dash underneath the More Suggestions header. If you do not wish to search this content source, you can disable this search plugin.\n", "screenshot": "http://ubuntuone.com/3VvxS1Dri9CKKloEzBs2lT", "name": "Skimlinks", "keywords": ["skimlinks"], "id": "more_suggestions-skimlinks.scope", "icon": "file:///usr/share/icons/unity-icon-theme/places/svg/group-treat-yourself.svg"}, {"description": "This is an Ubuntu search plugin that enables information from Wikipedia to be searched and displayed in the Dash underneath the Reference header. If you do not wish to search this content source, you can disable this search plugin.\n", "screenshot": "http://ubuntuone.com/4QiCFcHJS3fmDCMFvgi9lo", "name": "Wikipedia", "keywords": ["wikipedia", "wiki"], "id": "reference-wikipedia.scope", "icon": "file:///usr/share/icons/unity-icon-theme/places/svg/service-wikipedia.svg"}, {"description": "This is an Ubuntu search plugin that enables information from Wordnik to be searched and displayed in the Dash underneath the Reference header. If you do not wish to search this content source, you can disable this search plugin.\n", "screenshot": "http://ubuntuone.com/15gptcYAwD6vk53cUtOrvX", "name": "Dictionary", "keywords": ["define"], "id": "reference-dictionary.scope", "icon": "file:///usr/share/icons/unity-icon-theme/places/svg/service-wordnik.svg"}, {"description": "Find themoviedb.org items", "screenshot": null, "name": "themoviedb.org", "keywords": ["themoviedb", "movie", "db", "actor", "cinema"], "id": "reference-themoviedb.scope", "icon": null}, {"description": "This is an Ubuntu search plugin that enables information from Amazon to be searched and displayed in the Dash underneath the More Suggestions header. If you do not wish to search this content source, you can disable this search plugin.\n", "screenshot": "http://ubuntuone.com/5apoKe0t8OOj1BQkWKnfWi", "name": "Amazon", "keywords": ["amazon"], "id": "more_suggestions-amazon.scope", "icon": "file:///usr/share/icons/unity-icon-theme/places/svg/service-amazon.svg"}, {"description": "This is an Ubuntu search plugin that enables information from Etsy to be searched and displayed in the Dash underneath the More Suggestions header. If you do not wish to search this content source, you can disable this search plugin.", "screenshot": null, "name": "Etsy", "keywords": ["etsy"], "id": "more_suggestions-etsy.scope", "icon": null}, {"description": "This is an Ubuntu search plugin that enables information from eBay to be searched and displayed in the Dash underneath the More Suggestions header. If you do not wish to search this content source, you can disable this search plugin.", "screenshot": null, "name": "eBay", "keywords": ["ebay"], "id": "more_suggestions-ebay.scope", "icon": null}, {"description": "Find Stackexchange items", "screenshot": null, "name": "Stackexchange", "keywords": ["stackexchange"], "id": "reference-stackexchange.scope", "icon": null}]
166
167=== modified file 'tests/unit/Makefile.am'
168--- tests/unit/Makefile.am 2013-06-11 15:36:38 +0000
169+++ tests/unit/Makefile.am 2013-10-01 10:06:09 +0000
170@@ -19,6 +19,7 @@
171 --pkg glib-2.0 \
172 --pkg json-glib-1.0 \
173 --pkg gee-1.0 \
174+ --pkg unity-extras \
175 --vapidir $(srcdir) \
176 --vapidir $(top_srcdir)/vapi \
177 --target-glib=2.26 \
178@@ -36,7 +37,6 @@
179
180 AM_CPPFLAGS = \
181 -DDATADIR=\"$(DATADIR)\" \
182- -DPKGDATADIR=\"$(PKGDATADIR)\" \
183 -DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \
184 -DG_LOG_DOMAIN=\"unity-scope-home\" \
185 $(HOME_SCOPE_CFLAGS) \
186@@ -49,7 +49,7 @@
187 AM_CPPFLAGS += -w
188 endif
189
190-test_home_scope_VALASOURCES = \
191+MAINSOURCES = \
192 ../../src/scope.vala \
193 ../../src/scope-manager.vala \
194 ../../src/keyword-search.vala \
195@@ -74,11 +74,21 @@
196 ../../src/markup-cleaner.vala \
197 ../../src/master-scopes.vala \
198 ../../src/smart-scopes-preview-parser.vala \
199+ $(NULL)
200+
201+TEST_SOURCES = \
202 config-tests.vala \
203+ test-utils.vala \
204 test-home-scope.vala \
205 $(NULL)
206
207-nodist_test_home_scope_SOURCES = $(test_home_scope_VALASOURCES:.vala=.c)
208+test_home_scope_VALASOURCES = $(TEST_SOURCES) $(MAINSOURCES)
209+
210+# the patsubst rule below and objects override are needed to force dependency on *.c files
211+# generated in current test dir rather than on those from ../../src
212+nodist_test_home_scope_SOURCES = $(TEST_SOURCES:.vala=.c) $(patsubst ../../src/%,%,$(MAINSOURCES:.vala=.c))
213+nodist_test_home_scope_OBJECTS = $(nodist_test_home_scope_SOURCES:.c=.o)
214+
215
216 CLEANFILES = *.stamp \
217 *.c \
218@@ -96,6 +106,7 @@
219 $(AM_V_GEN)$(VALAC) -C $(AM_VALAFLAGS) $(VALAFLAGS) $^
220 @touch $@
221
222+
223 # START HEADLESS TESTS
224 if ENABLE_HEADLESS_TESTS
225 test-headless:
226
227=== renamed file 'tests/unit/data/client-scopes.json' => 'tests/unit/data/unity/client-scopes.json'
228=== modified file 'tests/unit/data/unity/scopes/masterscope_a.scope'
229--- tests/unit/data/unity/scopes/masterscope_a.scope 2013-01-23 10:48:55 +0000
230+++ tests/unit/data/unity/scopes/masterscope_a.scope 2013-10-01 10:06:09 +0000
231@@ -1,6 +1,6 @@
232 [Scope]
233-DBusName=com.canonical.Unity.Scope.MasterA
234-DBusPath=/com/canonical/unity/scope/mastera
235+DBusName=com.canonical.Unity.Scope.HomeTest
236+DBusPath=/com/canonical/unity/masterscopes/mastera
237 Icon=/usr/share/unity/6/icon1.svg
238 IsMaster=true
239 RequiredMetadata=some_id[s];foo[s];bar[y];
240
241=== modified file 'tests/unit/data/unity/scopes/masterscope_b.scope'
242--- tests/unit/data/unity/scopes/masterscope_b.scope 2013-01-23 10:48:55 +0000
243+++ tests/unit/data/unity/scopes/masterscope_b.scope 2013-10-01 10:06:09 +0000
244@@ -1,6 +1,6 @@
245 [Scope]
246-DBusName=com.canonical.Unity.Scope.MasterB
247-DBusPath=/com/canonical/unity/scope/masterb
248+DBusName=com.canonical.Unity.Scope.HomeTest
249+DBusPath=/com/canonical/unity/masterscopes/masterb
250 Icon=/usr/share/unity/6/icon2.svg
251 IsMaster=true
252 RequiredMetadata=bid[s];a[s];b[y];
253
254=== added file 'tests/unit/data/unity/scopes/more_suggestions.scope'
255--- tests/unit/data/unity/scopes/more_suggestions.scope 1970-01-01 00:00:00 +0000
256+++ tests/unit/data/unity/scopes/more_suggestions.scope 2013-10-01 10:06:09 +0000
257@@ -0,0 +1,17 @@
258+[Scope]
259+DBusName=com.canonical.Unity.Scope.HomeTest
260+DBusPath=/com/canonical/unity/masterscope/moresuggestions
261+Icon=
262+CategoryIcon=/usr/share/icons/unity-icon-theme/places/svg/group-treat-yourself.svg
263+Name=More suggestions
264+Type=moresug
265+IsMaster=true
266+
267+[Desktop Entry]
268+X-Ubuntu-Gettext-Domain=unity-scope-home
269+
270+[Category more_suggestions]
271+Name=More suggestions
272+Icon=
273+SortField=uri
274+
275
276=== added file 'tests/unit/data/unity/scopes/reference.scope'
277--- tests/unit/data/unity/scopes/reference.scope 1970-01-01 00:00:00 +0000
278+++ tests/unit/data/unity/scopes/reference.scope 2013-10-01 10:06:09 +0000
279@@ -0,0 +1,33 @@
280+[Scope]
281+DBusName=com.canonical.Unity.Scope.HomeTest
282+DBusPath=/com/canonical/unity/masterscope/reference
283+Icon=
284+CategoryIcon=/usr/share/icons/unity-icon-theme/places/svg/group-reference.svg
285+Name=Reference
286+Type=reference
287+IsMaster=true
288+Keywords=reference;
289+
290+[Desktop Entry]
291+X-Ubuntu-Gettext-Domain=unity-scope-home
292+
293+[Category encyclopedia]
294+Name=Encyclopedia
295+Icon=
296+DedupField=uri
297+
298+[Category vocabulary]
299+Name=Vocabulary
300+Icon=
301+DedupField=uri
302+
303+[Category scholar]
304+Name=Scholar
305+Icon=
306+DedupField=uri
307+
308+[Category sources]
309+Name=Sources
310+Icon=
311+DedupField=uri
312+
313
314=== modified file 'tests/unit/test-home-scope.vala'
315--- tests/unit/test-home-scope.vala 2013-09-19 15:22:33 +0000
316+++ tests/unit/test-home-scope.vala 2013-10-01 10:06:09 +0000
317@@ -17,55 +17,77 @@
318 */
319
320 using Unity.HomeScope.SmartScopes;
321+using Unity.Protocol;
322
323 namespace Unity.HomeScope
324 {
325
326+ static const string HOME_SCOPE_DBUS_NAME = "com.canonical.Unity.Scope.HomeTest";
327+ static const string HOME_SCOPE_DBUS_PATH = "/com/canonical/unity/home";
328+
329 bool verbose_debug = false;
330+
331+ HomeScope? scope = null;
332+ Application? app = null;
333
334- /* A bit of magic to get proper-ish fixture support */
335- public interface Fixture : Object
336- {
337- class DelegateWrapper
338- {
339- TestDataFunc func;
340- public DelegateWrapper (owned TestDataFunc f) { func = (owned) f; }
341- }
342-
343- public virtual void setup () {}
344- public virtual void teardown () {}
345-
346- [CCode (has_target = false)]
347- public delegate void Callback<T> (T ptr);
348-
349- private static List<DelegateWrapper> _tests;
350-
351- public static unowned TestDataFunc create<F> (Callback<void*> cb)
352- requires (typeof (F).is_a (typeof (Fixture)))
353- {
354- TestDataFunc functor = () =>
355- {
356- var type = typeof (F);
357- var instance = Object.new (type) as Fixture;
358- instance.setup ();
359- cb (instance);
360- instance.teardown ();
361- };
362- unowned TestDataFunc copy = functor;
363- _tests.append (new DelegateWrapper ((owned) functor));
364- return copy;
365- }
366- public static unowned TestDataFunc create_static<F> (Callback<F> cb)
367- {
368- return create<F> ((Callback<void*>) cb);
369- }
370- }
371-
372 private KeywordSearch kw;
373 const string[] RESULTS_SCHEMA = {"s", "s", "u", "u", "s", "s", "s", "s", "a{sv}"}; //TODO use schema def from libunity when it's public
374
375+ public class FakeSmartScopesServer
376+ {
377+ private Rand rand = new Rand ();
378+ private Pid server_pid;
379+ public int server_port { get; internal set; }
380+
381+ public void start () throws SpawnError
382+ {
383+ server_port = rand.int_range (1024, 9000);
384+
385+ Process.spawn_async (null, {Config.TOPSRCDIR + "/tests/fake-server/fake-sss-server.py",
386+ "--scopes", Config.TOPSRCDIR + "/tests/fake-server/samples/remote-scopes-minimal.txt",
387+ "--search", Config.TESTRUNDATADIR + "/search.dump",
388+ "--feedback", Config.TESTRUNDATADIR + "/feedback.dump",
389+ "--requests", "6",
390+ "--timeout", "15",
391+ "--port", server_port.to_string (),
392+ Config.TOPSRCDIR + "/tests/unit/data/search_results1.txt"},
393+ null, 0, null, out server_pid);
394+
395+ Socket socket = new Socket (SocketFamily.IPV4, SocketType.STREAM, SocketProtocol.TCP);
396+ assert (socket != null);
397+
398+ InetAddress addr = new InetAddress.from_bytes ({127, 0, 0, 1}, SocketFamily.IPV4);
399+ InetSocketAddress server_addr = new InetSocketAddress (addr, (uint16)server_port);
400+
401+ bool conn = false;
402+ int retry = 5;
403+ while (!conn && retry > 0)
404+ {
405+ try
406+ {
407+ conn = socket.connect (server_addr);
408+ }
409+ catch (Error e) {}
410+ if (!conn)
411+ Thread.usleep (1*1000000); // sleep for 1 second
412+ --retry;
413+ }
414+ }
415+
416+ public void stop ()
417+ {
418+ Posix.kill (server_pid, Posix.SIGTERM);
419+ Process.close_pid (server_pid);
420+ }
421+ }
422+
423+ FakeSmartScopesServer fake_server;
424+
425 public static int main (string[] args)
426 {
427+ Environment.set_variable ("GSETTINGS_BACKEND", "memory", true);
428+ Environment.set_variable ("HOME_SCOPE_IGNORE_OFONO", "1", true);
429+
430 var xdg_data_dirs = Environment.get_variable ("XDG_DATA_DIRS");
431 if (xdg_data_dirs == null) xdg_data_dirs = "/usr/share";
432 Environment.set_variable ("XDG_DATA_DIRS",
433@@ -74,10 +96,14 @@
434 Environment.set_variable ("LIBUNITY_SCOPE_DIRECTORIES",
435 "%s/unity/scopes".printf (Config.TESTDATADIR),
436 true);
437+
438+ fake_server = new FakeSmartScopesServer ();
439+ fake_server.start ();
440+
441 kw = new KeywordSearch ();
442
443 Test.init (ref args);
444-
445+
446 Test.add_data_func ("/Unit/Search/ScopeRegistry", Fixture.create<HomeScopeSearchTester> (HomeScopeSearchTester.test_registry));
447 Test.add_data_func ("/Unit/Search/KeywordSearch", Fixture.create<HomeScopeSearchTester> (HomeScopeSearchTester.test_keyword_search));
448 Test.add_data_func ("/Unit/Search/SearchQueryState", Fixture.create<HomeScopeSearchTester> (HomeScopeSearchTester.test_search_query_state));
449@@ -131,37 +157,153 @@
450 Test.add_data_func ("/Unit/MarkupCleaner/UnsupportedEntitiesAreRaw", Fixture.create<MarkupCleanerTester> (MarkupCleanerTester.test_unsupported_entities_are_raw));
451 Test.add_data_func ("/Unit/MarkupCleaner/NumericEntitiesArePreserved", Fixture.create<MarkupCleanerTester> (MarkupCleanerTester.test_num_entities_are_preserved));
452
453- var ml = new MainLoop ();
454- ScopeRegistry.instance ().find_scopes.begin ((obj, res) =>
455+ Test.add_data_func ("/Unit/HomeScopeInstance/PhoneFilters", Fixture.create<HomeScopeInstanceTester> (HomeScopeInstanceTester.test_phone_filters));
456+ Test.add_data_func ("/Unit/HomeScopeInstance/DesktopFilters", Fixture.create<HomeScopeInstanceTester> (HomeScopeInstanceTester.test_desktop_filters));
457+
458+ Environment.set_variable ("SMART_SCOPES_SERVER", "http://127.0.0.1:%d".printf (fake_server.server_port), true);
459+
460+ HomeScope.discover_scopes_sync ();
461+
462+ app = Extras.dbus_own_name (HOME_SCOPE_DBUS_NAME, () =>
463 {
464- ScopeRegistry.instance ().find_scopes.end (res);
465- kw.rebuild ();
466- ml.quit ();
467+ scope = new HomeScope ();
468 });
469- ml.run ();
470- MetaScopeRegistry.instance ().update (ScopeRegistry.instance (), null);
471+ kw.rebuild ();
472
473 Test.run ();
474
475+ fake_server.stop ();
476+
477 return 0;
478 }
479-
480- public static bool run_with_timeout (MainLoop ml, uint timeout_ms = 5000)
481+
482+ class HomeScopeInstanceTester: Object, Fixture
483 {
484- bool timeout_reached = false;
485- var t_id = Timeout.add (timeout_ms, () =>
486- {
487- timeout_reached = true;
488- debug ("Timeout reached");
489- ml.quit ();
490- return false;
491- });
492-
493- ml.run ();
494-
495- if (!timeout_reached) Source.remove (t_id);
496-
497- return !timeout_reached;
498+ ScopeProxy? proxy = null;
499+ string channel_id;
500+
501+ private void setup ()
502+ {
503+ if (!scope.smart_scopes_ready) {
504+ var ml = new MainLoop ();
505+ scope.notify["smart_scopes_ready"].connect(() => { ml.quit (); });
506+ run_with_timeout (ml, 8);
507+ }
508+
509+ proxy = acquire_test_proxy (HOME_SCOPE_DBUS_NAME, HOME_SCOPE_DBUS_PATH);
510+ channel_id = open_channel (proxy, ChannelType.GLOBAL, null);
511+ assert (channel_id != null);
512+ }
513+
514+ private void teardown ()
515+ {
516+ proxy.close_channel (channel_id, null);
517+ proxy = null;
518+ }
519+
520+ internal void test_phone_filters ()
521+ {
522+ // ignore warnings
523+ Test.log_set_fatal_handler (() => { return false; });
524+
525+ bool got_filters_update = false;
526+
527+ proxy.filter_settings_changed.connect ((chid, filter_rows) => {
528+ got_filters_update = true;
529+ });
530+
531+ var hints = new HashTable<string, Variant> (str_hash, str_equal);
532+ hints["form-factor"] = "phone";
533+ perform_search (proxy, channel_id, "metallica", hints, null);
534+
535+ assert (got_filters_update == false); // we expect no filter updates on the phone
536+ }
537+
538+ internal void test_desktop_filters ()
539+ {
540+ // ignore warnings
541+ Test.log_set_fatal_handler (() => { return false; });
542+
543+ int got_filters_update = 0;
544+
545+ Variant? filters = null;
546+ proxy.filter_settings_changed.connect ((chid, filter_rows) => {
547+ got_filters_update += 1;
548+ // note: there are 2 filter updates during search, but we care about the final value of filters
549+ filters = filter_rows;
550+ });
551+
552+ assert (channel_id != null);
553+ assert (got_filters_update == 0);
554+
555+ wait_for_synchronization (proxy.filters_model);
556+ assert (proxy.filters_model.get_n_rows () == 2);
557+
558+ // verify all filter options are initially inactive
559+ int option_count = 0;
560+ for (var iter = proxy.filters_model.get_first_iter (); iter != proxy.filters_model.get_last_iter (); iter = proxy.filters_model.next (iter))
561+ {
562+ var opts = proxy.filters_model.get_row (iter)[4].lookup_value ("options", null);
563+ for (int i = 0; i<opts.n_children (); i++)
564+ {
565+ option_count += 1;
566+ var opt = opts.get_child_value(i);
567+ assert (opt.get_child_value (3).get_boolean () == false);
568+ }
569+ }
570+
571+ assert (option_count == 17);
572+
573+ var hints = new HashTable<string, Variant> (str_hash, str_equal);
574+ hints["form-factor"] = "desktop";
575+ perform_search (proxy, channel_id, "iron maiden", hints, null);
576+
577+ assert (got_filters_update == 2);
578+ assert (filters.n_children() == 2); // two filters ('sources' and 'categories')
579+ var src_filter = filters.get_child_value(0);
580+ var cat_filter = filters.get_child_value(1);
581+
582+ // verify 'sources' filter
583+ var opts = src_filter.get_child_value (4).lookup_value ("options", null);
584+ var option_flags = new HashTable<string, bool>(str_hash, str_equal);
585+
586+ for (int i = 0; i<opts.n_children (); i++) // create scopeid -> enabled flag lookup for sources filter
587+ {
588+ var opt = opts.get_child_value(i);
589+ option_flags[opt.get_child_value (0).get_string ()] = opt.get_child_value (3).get_boolean ();
590+ }
591+
592+ assert (option_flags["reference-stackexchange.scope"] == false);
593+ assert (option_flags["reference-dictionary.scope"] == false);
594+ assert (option_flags["reference-themoviedb.scope"] == false);
595+ assert (option_flags["masterscope_b-subscope1.scope"] == false);
596+ assert (option_flags["masterscope_b-subscope2.scope"] == false);
597+ assert (option_flags["masterscope_a-subscope1.scope"] == false);
598+ assert (option_flags["masterscope_a-subscope2.scope"] == false);
599+ assert (option_flags["more_suggestions-amazon.scope"] == false);
600+ assert (option_flags["more_suggestions-etsy.scope"] == false);
601+ assert (option_flags["more_suggestions-ebay.scope"] == false);
602+ assert (option_flags["more_suggestions-skimlinks.scope"] == false);
603+
604+ assert (option_flags["more_suggestions-u1ms.scope"] == true);
605+ assert (option_flags["reference-wikipedia.scope"] == true);
606+
607+ // verify 'categories' filter
608+ opts = cat_filter.get_child_value (4).lookup_value ("options", null);
609+ option_flags = new HashTable<string, bool>(str_hash, str_equal);
610+
611+ for (int i = 0; i<opts.n_children (); i++) // create scopeid -> enabled flag lookup for categories filter
612+ {
613+ var opt = opts.get_child_value(i);
614+ option_flags[opt.get_child_value (0).get_string ()] = opt.get_child_value (3).get_boolean ();
615+ }
616+
617+ assert (option_flags["masterscope_a.scope"] == false);
618+ assert (option_flags["masterscope_b.scope"] == false);
619+
620+ assert (option_flags["more_suggestions.scope"] == true);
621+ assert (option_flags["reference.scope"] == true);
622+ }
623 }
624
625 class HomeScopeSearchTester: Object, Fixture
626@@ -178,7 +320,7 @@
627 // scope registry is a singleton and is initialized in main on start
628 var registry = ScopeRegistry.instance ();
629 var scopes = registry.flatten ();
630- assert (scopes.size == 6);
631+ assert (scopes.size == 8);
632 assert (scopes.contains ("masterscope_a.scope"));
633 assert (scopes.contains ("masterscope_b.scope"));
634 assert (scopes.contains ("masterscope_a-subscope1.scope"));
635@@ -189,7 +331,7 @@
636
637 internal void test_keyword_search ()
638 {
639- assert (kw.num_of_mappings == 8);
640+ assert (kw.num_of_mappings == 9);
641 string new_search_string;
642
643 assert (kw.process_query ("abcd: foobar", out new_search_string) == null); //unknown keyword, leave query as is
644@@ -652,6 +794,7 @@
645 internal void test_smart_scopes_parse ()
646 {
647 CategoryManager.instance ().register ("more_suggestions.scope");
648+ CategoryManager.instance ().register ("reference.scope");
649
650 int row_count = 0;
651 int recommend_count = 0;
652@@ -783,6 +926,8 @@
653 internal void test_smart_scopes_on_chunk_data ()
654 {
655 CategoryManager.instance ().register ("more_suggestions.scope");
656+ CategoryManager.instance ().register ("reference.scope");
657+
658 int recommend_count = 0;
659 int result_count = 0;
660
661@@ -1003,7 +1148,7 @@
662 installed.add ("scope2.scope");
663 installed.add ("scope4.scope");
664
665- var clinfo = ClientScopesInfo.from_file (Config.TOPSRCDIR + "/tests/unit/data/client-scopes.json", installed);
666+ var clinfo = ClientScopesInfo.from_file (Config.TOPSRCDIR + "/tests/unit/data/unity/client-scopes.json", installed);
667 var added = clinfo.get_added_scopes ();
668 var removed = clinfo.get_removed_scopes ();
669
670@@ -1062,59 +1207,12 @@
671
672 class SmartScopesInterfaceTester: Object, Fixture
673 {
674- private Rand rand = new Rand ();
675- private Pid server_pid;
676- private int server_port;
677-
678- private void start_fake_server () throws SpawnError
679- {
680- server_port = rand.int_range (1024, 9000);
681-
682- Process.spawn_async (null, {Config.TOPSRCDIR + "/tests/fake-server/fake-sss-server.py",
683- "--scopes", Config.TOPSRCDIR + "/tests/fake-server/samples/remote-scopes.txt",
684- "--search", Config.TESTRUNDATADIR + "/search.dump",
685- "--feedback", Config.TESTRUNDATADIR + "/feedback.dump",
686- "--requests", "2",
687- "--timeout", "5",
688- "--port", server_port.to_string (),
689- Config.TOPSRCDIR + "/tests/unit/data/search_results1.txt"},
690- null, 0, null, out server_pid);
691-
692- Socket socket = new Socket (SocketFamily.IPV4, SocketType.STREAM, SocketProtocol.TCP);
693- assert (socket != null);
694-
695- InetAddress addr = new InetAddress.from_bytes ({127, 0, 0, 1}, SocketFamily.IPV4);
696- InetSocketAddress server_addr = new InetSocketAddress (addr, (uint16)server_port);
697-
698- bool conn = false;
699- int retry = 5;
700- while (!conn && retry > 0)
701- {
702- try
703- {
704- conn = socket.connect (server_addr);
705- }
706- catch (Error e) {}
707- if (!conn)
708- Thread.usleep (1*1000000); // sleep for 1 second
709- --retry;
710- }
711- Process.close_pid (server_pid);
712- }
713-
714- private void setup ()
715- {
716- start_fake_server ();
717- }
718-
719- private void teardown ()
720- {
721- Posix.kill (server_pid, Posix.SIGTERM);
722- }
723-
724 internal void test_smart_scopes_client_iface_search ()
725 {
726- Environment.set_variable ("SMART_SCOPES_SERVER", "http://127.0.0.1:%d".printf (server_port), true);
727+ // ignore warnings
728+ Test.log_set_fatal_handler (() => { return false; });
729+
730+ Environment.set_variable ("SMART_SCOPES_SERVER", "http://127.0.0.1:%d".printf (fake_server.server_port), true);
731 var pinfo = new SmartScopes.PlatformInfo.with_data ("1304", "EN", {"scope1"}, {"scope3"});
732 var client = new SmartScopes.SmartScopesClient (pinfo);
733 var session_id = "5d06cc10-751b-11e2-87e0-fb468b0a185a";
734@@ -1159,7 +1257,10 @@
735
736 internal void test_smart_scopes_client_iface_metrics ()
737 {
738- Environment.set_variable ("SMART_SCOPES_SERVER", "http://127.0.0.1:%d".printf (server_port), true);
739+ // ignore warnings
740+ Test.log_set_fatal_handler (() => { return false; });
741+
742+ Environment.set_variable ("SMART_SCOPES_SERVER", "http://127.0.0.1:%d".printf (fake_server.server_port), true);
743 var pinfo = new SmartScopes.PlatformInfo.with_data ("1304", "EN", {"scope1"}, {"scope3"});
744 var client = new SmartScopes.SmartScopesClient (pinfo);
745 var session_id = "5d06cc10-751b-11e2-87e0-fb468b0a185a";
746@@ -1245,6 +1346,9 @@
747
748 internal void test_smart_scopes_client_iface_error ()
749 {
750+ // ignore warnings
751+ Test.log_set_fatal_handler (() => { return false; });
752+
753 Environment.set_variable ("SMART_SCOPES_SERVER", "http://127.0.0.1:9999", true); // non-existing server (connection failure)
754 var pinfo = new SmartScopes.PlatformInfo.with_data ("1304", "EN", {"scope1"}, {"scope3"});
755 var client = new SmartScopes.SmartScopesClient (pinfo);
756
757=== added file 'tests/unit/test-utils.vala'
758--- tests/unit/test-utils.vala 1970-01-01 00:00:00 +0000
759+++ tests/unit/test-utils.vala 2013-10-01 10:06:09 +0000
760@@ -0,0 +1,194 @@
761+/*
762+ * Copyright (C) 2011-2013 Canonical Ltd
763+ *
764+ * This program is free software: you can redistribute it and/or modify
765+ * it under the terms of the GNU General Public License version 3 as
766+ * published by the Free Software Foundation.
767+ *
768+ * This program is distributed in the hope that it will be useful,
769+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
770+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
771+ * GNU General Public License for more details.
772+ *
773+ * You should have received a copy of the GNU General Public License
774+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
775+ *
776+ * Authored by Michal Hruby <michal.hruby@canonical.com>
777+ *
778+ */
779+
780+using Unity.Protocol;
781+
782+public static bool run_with_timeout (MainLoop ml, uint timeout_ms = 5000)
783+{
784+ bool timeout_reached = false;
785+ var t_id = Timeout.add (timeout_ms, () =>
786+ {
787+ timeout_reached = true;
788+ debug ("Timeout reached");
789+ ml.quit ();
790+ return false;
791+ });
792+
793+ ml.run ();
794+
795+ if (!timeout_reached) Source.remove (t_id);
796+
797+ return !timeout_reached;
798+}
799+
800+/* A bit of magic to get proper-ish fixture support */
801+public interface Fixture : Object
802+{
803+ class DelegateWrapper
804+ {
805+ TestDataFunc func;
806+ public DelegateWrapper (owned TestDataFunc f) { func = (owned) f; }
807+ }
808+
809+ public virtual void setup () {}
810+ public virtual void teardown () {}
811+
812+ [CCode (has_target = false)]
813+ public delegate void Callback<T> (T ptr);
814+
815+ private static List<DelegateWrapper> _tests;
816+
817+ public static unowned TestDataFunc create<F> (Callback<void*> cb)
818+ requires (typeof (F).is_a (typeof (Fixture)))
819+ {
820+ TestDataFunc functor = () =>
821+ {
822+ var type = typeof (F);
823+ var instance = Object.new (type) as Fixture;
824+ instance.setup ();
825+ cb (instance);
826+ instance.teardown ();
827+ };
828+ unowned TestDataFunc copy = functor;
829+ _tests.append (new DelegateWrapper ((owned) functor));
830+ return copy;
831+ }
832+ public static unowned TestDataFunc create_static<F> (Callback<F> cb)
833+ {
834+ return create<F> ((Callback<void*>) cb);
835+ }
836+}
837+
838+// this will auto-disconnect signals when it goes out of scope
839+public class SignalWrapper
840+{
841+ unowned Object obj;
842+ ulong sig_id;
843+
844+ public SignalWrapper (Object o, ulong signal_id)
845+ {
846+ obj = o;
847+ sig_id = signal_id;
848+ }
849+
850+ ~SignalWrapper ()
851+ {
852+ SignalHandler.disconnect (obj, sig_id);
853+ }
854+}
855+
856+public static ScopeProxy? acquire_test_proxy (string name, string path)
857+{
858+ var ml = new MainLoop ();
859+ ScopeProxy? proxy = null;
860+ ScopeProxy.new_from_dbus.begin (name, path, null, (obj, res) =>
861+ {
862+ try
863+ {
864+ proxy = ScopeProxy.new_from_dbus.end (res);
865+ }
866+ catch (Error e) {}
867+ ml.quit ();
868+ });
869+ assert (run_with_timeout (ml));
870+ return proxy;
871+}
872+
873+public static void wait_for_synchronization (Dee.Model model)
874+{
875+ var shared_model = model as Dee.SharedModel;
876+ if (shared_model == null) return;
877+
878+ if (shared_model.is_synchronized ()) return;
879+ SignalWrapper[] signals = {};
880+ var ml = new MainLoop ();
881+
882+ signals += new SignalWrapper (shared_model,
883+ shared_model.notify["synchronized"].connect (() =>
884+ {
885+ ml.quit ();
886+ }));
887+
888+ run_with_timeout (ml);
889+}
890+
891+public static string open_channel (ScopeProxy proxy,
892+ ChannelType channel_type,
893+ out Dee.SerializableModel model,
894+ bool wait_for_sync = false,
895+ ChannelFlags flags = 0)
896+{
897+ string? channel_id = null;
898+ Dee.Model? real_model = null;
899+ var ml = new MainLoop ();
900+ /* Need to use PRIVATE channel, cause standard SharedModel won't
901+ * synchronize properly when trying to connect to the model
902+ * from the same process (/bus address) */
903+ proxy.open_channel.begin (channel_type,
904+ flags | ChannelFlags.PRIVATE,
905+ null,
906+ (obj, res) =>
907+ {
908+ try
909+ {
910+ channel_id = proxy.open_channel.end (res, out real_model);
911+ if (wait_for_sync)
912+ {
913+ wait_for_synchronization (real_model);
914+ }
915+ ml.quit ();
916+ }
917+ catch (Error err)
918+ {
919+ ml.quit ();
920+ }
921+ });
922+
923+ assert (run_with_timeout (ml));
924+ assert (channel_id != null);
925+ model = real_model as Dee.SerializableModel;
926+ return channel_id;
927+}
928+
929+public static HashTable<string, Variant> perform_search (
930+ ScopeProxy proxy, string channel_id, string query,
931+ HashTable<string, Variant>? hints = null,
932+ Dee.SerializableModel? model = null)
933+{
934+ var ml = new MainLoop ();
935+ HashTable<string, Variant>? reply_dict = null;
936+ proxy.search.begin (channel_id, query,
937+ hints ?? new HashTable<string, Variant> (null, null),
938+ null,
939+ (obj, res) =>
940+ {
941+ try
942+ {
943+ reply_dict = proxy.search.end (res);
944+ }
945+ catch (Error err) {}
946+ ml.quit ();
947+ });
948+
949+ bool got_search_signal = false;
950+
951+ assert (run_with_timeout (ml, 10000));
952+ assert (reply_dict != null);
953+ return reply_dict;
954+}

Subscribers

People subscribed via source and target branches

to all changes: