Merge lp:~mhr3/libunity/test-tool into lp:libunity

Proposed by Michal Hruby
Status: Merged
Approved by: Didier Roche-Tolomelli
Approved revision: 118
Merged at revision: 116
Proposed branch: lp:~mhr3/libunity/test-tool
Merge into: lp:libunity
Diff against target: 580 lines (+548/-0)
4 files modified
Makefile.am (+1/-0)
configure.ac (+1/-0)
tools/Makefile.am (+54/-0)
tools/unity-tool.vala (+492/-0)
To merge this branch: bzr merge lp:~mhr3/libunity/test-tool
Reviewer Review Type Date Requested Status
Didier Roche-Tolomelli Approve
Mikkel Kamstrup Erlandsen (community) Needs Fixing
Review via email: mp+91027@code.launchpad.net

Description of the change

Added unity-lens-test-tool and implemented its basic functionality.

To post a comment you must log in.
Revision history for this message
Mikkel Kamstrup Erlandsen (kamstrup) wrote :

Looking really good, and fun to play with :-D

Some remarks:

 - I'd like to use this binary as a generic place to shove Unity cli introspection and instrumentation for all future needs we are goona have, hence I'd like to rename it to something more generic like 'unity-tool'. This way it can possibly also satisfy cli junkies' desire to drive unity from the command line

 - When running the executable without arguments could you dump the -h info instead? That seems to be the general pattern for cli tools

 - the --common-tests suite is just insanely awesome, let's be mindful of adding any general tests we can come up with there

 - about --test-server-mode; it's not clear to me what this does? And running the tool with it just reports "No test cases specified"

review: Needs Fixing
Revision history for this message
Mikkel Kamstrup Erlandsen (kamstrup) wrote :

More bits:

314 + catch (Error err)
315 + {
316 + warning ("%s", err.message);
317 + return 1;
318 + }

Can this just do print ("unity-tool: %s", er.message); instead of warning()? The G logging systems appends some odd debugging info (like filename and line) that seems out of place for these messages.

And, the --test-server-mode; can we have a more descriptive string somehow? Maybe ala "Run a collection of test scripts"

Revision history for this message
Michal Hruby (mhr3) wrote :

Pushed fixes.

Revision history for this message
Mikkel Kamstrup Erlandsen (kamstrup) wrote :

Perfect! (almost :-))

191 + "Run a collection of test sceripts", null

review: Needs Fixing
Revision history for this message
Michal Hruby (mhr3) wrote :

And... fixed!

Revision history for this message
Mikkel Kamstrup Erlandsen (kamstrup) wrote :

On the one!

review: Approve
Revision history for this message
Unity Merger (unity-merger) wrote :

The Jenkins job https://jenkins.qa.ubuntu.com/job/automerge-libunity/46/console reported an error when processing this lp:~mhr3/libunity/test-tool branch.
Not merging it.

Revision history for this message
Mikkel Kamstrup Erlandsen (kamstrup) wrote :

The bot reports:
  dh_install: usr/bin/unity-tool exists in debian/tmp but is not installed to anywhere
  dh_install: missing files, aborting

We should ping Didier to have unity-tool handled.

review: Needs Fixing
Revision history for this message
Didier Roche-Tolomelli (didrocks) wrote :

You wanted it? You got it! (It's installed in libunity-dev package now)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile.am'
2--- Makefile.am 2011-11-28 09:28:37 +0000
3+++ Makefile.am 2012-02-02 08:34:18 +0000
4@@ -2,6 +2,7 @@
5 data \
6 src \
7 bindings \
8+ tools \
9 doc \
10 examples \
11 test \
12
13=== modified file 'configure.ac'
14--- configure.ac 2012-01-12 15:13:36 +0000
15+++ configure.ac 2012-02-02 08:34:18 +0000
16@@ -164,6 +164,7 @@
17 doc/reference/Makefile
18 examples/Makefile
19 src/Makefile
20+tools/Makefile
21 test/Makefile
22 test/C/Makefile
23 test/vala/Makefile
24
25=== added directory 'tools'
26=== added file 'tools/Makefile.am'
27--- tools/Makefile.am 1970-01-01 00:00:00 +0000
28+++ tools/Makefile.am 2012-02-02 08:34:18 +0000
29@@ -0,0 +1,54 @@
30+NULL =
31+BUILT_SOURCES =
32+CLEANFILES =
33+EXTRA_DIST =
34+
35+bin_PROGRAMS = \
36+ unity-tool
37+
38+unity_tool_CPPFLAGS = \
39+ -DG_LOG_DOMAIN=\"unity-tool\" \
40+ -I$(srcdir) \
41+ $(LIBUNITY_CFLAGS)
42+
43+if !ENABLE_C_WARNINGS
44+ unity_tool_CPPFLAGS += -w
45+endif
46+
47+if ENABLE_TRACE_LOG
48+ unity_tool_CPPFLAGS += -DENABLE_UNITY_TRACE_LOG
49+endif
50+
51+unity_tool_LDADD = \
52+ $(LIBUNITY_LIBS)
53+
54+unity_tool_VALAFLAGS = \
55+ -C \
56+ --vapidir $(top_srcdir)/vapi \
57+ --pkg config \
58+ $(LIBUNITY_PACKAGES)
59+ $(MAINTAINER_VALAFLAGS)
60+
61+unity_tool_VALASOURCES = \
62+ unity-tool.vala \
63+ $(NULL)
64+
65+unity_tool_SOURCES = \
66+ $(unity_tool_VALASOURCES:.vala=.c) \
67+ $(NULL)
68+
69+BUILT_SOURCES += unity_tool_vala.stamp
70+EXTRA_DIST += \
71+ $(BUILT_SOURCES) \
72+ $(unity_tool_VALASOURCES) \
73+ $(NULL)
74+
75+unity_tool_vala.stamp: $(unity_tool_VALASOURCES)
76+ $(AM_V_GEN) $(VALAC) $(unity_tool_VALAFLAGS) $^
77+ @touch $@
78+
79+CLEANFILES += \
80+ *.stamp \
81+ $(unity_tool_VALASOURCES:.vala=.c) \
82+ $(NULL)
83+
84
85=== added file 'tools/unity-tool.vala'
86--- tools/unity-tool.vala 1970-01-01 00:00:00 +0000
87+++ tools/unity-tool.vala 2012-02-02 08:34:18 +0000
88@@ -0,0 +1,492 @@
89+/*
90+ * Copyright (C) 2012 Canonical Ltd
91+ *
92+ * This program is free software: you can redistribute it and/or modify
93+ * it under the terms of the GNU General Public License version 3 as
94+ * published by the Free Software Foundation.
95+ *
96+ * This program is distributed in the hope that it will be useful,
97+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
98+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
99+ * GNU General Public License for more details.
100+ *
101+ * You should have received a copy of the GNU General Public License
102+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
103+ *
104+ * Authored by Michal Hruby <michal.hruby@canonical.com>
105+ *
106+ */
107+
108+namespace Unity.Tester
109+{
110+ namespace Options
111+ {
112+ public static string lens_dbus_name;
113+ public static string lens_dbus_path;
114+ public static string lens_file;
115+ public static string search_string;
116+ public static int search_type;
117+ public static bool common_tests;
118+ public static bool no_search_reply;
119+ public static bool dump_results;
120+ public static bool dump_filters;
121+
122+ public static bool test_server_mode;
123+ public static string[] test_cases;
124+ }
125+
126+ namespace TestRunner
127+ {
128+ public static string[] test_cases;
129+ public static int test_index;
130+ }
131+
132+ public errordomain TesterError
133+ {
134+ INVALID_ARGS
135+ }
136+
137+ public struct LensInfo
138+ {
139+ public string dbus_path;
140+ public bool search_in_global;
141+ public bool visible;
142+ public string search_hint;
143+ public string private_connection_name;
144+ public string results_model_name;
145+ public string global_results_model_name;
146+ public string categories_model_name;
147+ public string filters_model_name;
148+ public HashTable<string, Variant> hints;
149+ }
150+
151+ const OptionEntry[] options =
152+ {
153+ {
154+ "dbus-name", 'n', 0, OptionArg.STRING, out Options.lens_dbus_name,
155+ "Unique dbus name of the tested lens", null
156+ },
157+ {
158+ "dbus-path", 'p', 0, OptionArg.STRING, out Options.lens_dbus_path,
159+ "Object path of the lens", null
160+ },
161+ {
162+ "lens-file", 'l', 0, OptionArg.STRING, out Options.lens_file,
163+ "Path to the lens file (to read out dbus name and path)", null
164+ },
165+ {
166+ "common-tests", 'c', 0, OptionArg.NONE, out Options.common_tests,
167+ "Perform common tests each lens should conform to", null
168+ },
169+ {
170+ "search", 's', 0, OptionArg.STRING, out Options.search_string,
171+ "Search string to send to the lens", null
172+ },
173+ {
174+ "search-type", 't', 0, OptionArg.INT, out Options.search_type,
175+ "Type of the search (value from Unity.SearchType enum)", null
176+ },
177+ {
178+ "dump-results", 'r', 0, OptionArg.NONE, out Options.dump_results,
179+ "Output the results model on stdout", null
180+ },
181+ {
182+ "dump-filters", 'f', 0, OptionArg.NONE, out Options.dump_filters,
183+ "Output the filter model on stdout", null
184+ },
185+ {
186+ "no-search-reply", 0, 0, OptionArg.NONE, out Options.no_search_reply,
187+ "Don't output reply of the search call", null
188+ },
189+ {
190+ "test-server-mode", 0, 0, OptionArg.NONE, out Options.test_server_mode,
191+ "Run a collection of test scripts", null
192+ },
193+ {
194+ "", 0, 0, OptionArg.FILENAME_ARRAY, out Options.test_cases,
195+ "Invididual test cases", "<test-scripts>"
196+ },
197+ {
198+ null
199+ }
200+ };
201+
202+ public static void warn (string format, ...)
203+ {
204+ var args = va_list ();
205+ logv ("unity-tool", LogLevelFlags.LEVEL_WARNING, format, args);
206+ }
207+
208+ public static int main (string[] args)
209+ {
210+ Environment.set_prgname ("unity-tool");
211+ var opt_context = new OptionContext (" - Unity tool");
212+ opt_context.add_main_entries (options, null);
213+
214+ try
215+ {
216+ if (args.length <= 1)
217+ {
218+ print ("%s\n", opt_context.get_help (true, null));
219+ return 0;
220+ }
221+
222+ opt_context.parse (ref args);
223+ if (Options.test_server_mode)
224+ {
225+ if (Options.test_cases == null ||
226+ (Options.test_cases.length=(int)strv_length(Options.test_cases)) == 0)
227+ {
228+ throw new TesterError.INVALID_ARGS ("No test cases specified");
229+ }
230+
231+ // special mode where we just run test scripts inside a directory
232+ string[] test_scripts = get_test_cases ();
233+ TestRunner.test_cases = test_scripts;
234+
235+ Test.init (ref args);
236+ foreach (unowned string test_case in test_scripts)
237+ {
238+ Test.add_data_func ("/Integration/LensTest/" +
239+ Path.get_basename (test_case),
240+ () =>
241+ {
242+ string test = TestRunner.test_cases[TestRunner.test_index++];
243+ int status;
244+ try
245+ {
246+ Process.spawn_command_line_sync (test,
247+ null,
248+ null,
249+ out status);
250+ }
251+ catch (Error e)
252+ {
253+ warn ("%s", e.message);
254+ status = -1;
255+ }
256+ assert (status == 0);
257+ });
258+ }
259+ return Test.run ();
260+ }
261+ else
262+ {
263+ // read dbus name and path from the lens file
264+ if (Options.lens_file != null)
265+ {
266+ var keyfile = new KeyFile ();
267+ keyfile.load_from_file (Options.lens_file, 0);
268+
269+ Options.lens_dbus_name = keyfile.get_string ("Lens", "DBusName");
270+ Options.lens_dbus_path = keyfile.get_string ("Lens", "DBusPath");
271+ }
272+
273+ // check that we have dbus names
274+ if (Options.lens_dbus_name == null || Options.lens_dbus_path == null)
275+ {
276+ throw new TesterError.INVALID_ARGS ("Lens DBus name and path not specified!");
277+ }
278+
279+ if (Options.common_tests)
280+ {
281+ int status = run_common_tests ();
282+ assert (status == 0);
283+ }
284+
285+ // Performing search
286+ if (Options.search_string != null)
287+ {
288+ var ml = new MainLoop ();
289+
290+ call_lens_search (Options.search_string,
291+ Options.search_type,
292+ (result) =>
293+ {
294+ if (!Options.no_search_reply)
295+ print ("%s\n", result.print (true));
296+ ml.quit ();
297+ });
298+
299+ assert (run_with_timeout (ml, 15000));
300+ }
301+
302+ // Dumping models
303+ if (Options.dump_results || Options.dump_filters)
304+ {
305+ LensInfo li = get_lens_info ();
306+
307+ if (Options.dump_results)
308+ {
309+ var model_name = Options.search_type == 0 ?
310+ li.results_model_name : li.global_results_model_name;
311+ var model = new Dee.SharedModel (model_name);
312+ sync_model (model);
313+
314+ dump_results_model (model);
315+ }
316+
317+ if (Options.dump_filters)
318+ {
319+ var model = new Dee.SharedModel (li.filters_model_name);
320+ sync_model (model);
321+
322+ dump_filters_model (model);
323+ }
324+ }
325+ }
326+ }
327+ catch (Error err)
328+ {
329+ warn ("%s", err.message);
330+ return 1;
331+ }
332+
333+
334+ return 0;
335+ }
336+
337+ public static string[] get_test_cases ()
338+ {
339+ string[] results = {};
340+ foreach (string path in Options.test_cases)
341+ {
342+ if (FileUtils.test (path, FileTest.IS_REGULAR) &&
343+ FileUtils.test (path, FileTest.IS_EXECUTABLE))
344+ {
345+ results += path;
346+ }
347+ else if (FileUtils.test (path, FileTest.IS_DIR))
348+ {
349+ try
350+ {
351+ var dir = Dir.open (path);
352+ unowned string name = dir.read_name ();
353+ while (name != null)
354+ {
355+ var child_path = Path.build_filename (path, name, null);
356+ if (FileUtils.test (child_path, FileTest.IS_REGULAR) &&
357+ FileUtils.test (child_path, FileTest.IS_EXECUTABLE))
358+ {
359+ results += child_path;
360+ }
361+
362+ name = dir.read_name ();
363+ }
364+ } catch (Error e) { warn ("%s", e.message); }
365+ }
366+ }
367+
368+ return results;
369+ }
370+
371+ public static bool run_with_timeout (MainLoop ml, uint timeout_ms)
372+ {
373+ bool timeout_reached = false;
374+ var t_id = Timeout.add (timeout_ms, () =>
375+ {
376+ timeout_reached = true;
377+ debug ("Timeout reached");
378+ ml.quit ();
379+ return false;
380+ });
381+
382+ ml.run ();
383+
384+ if (!timeout_reached) Source.remove (t_id);
385+
386+ return !timeout_reached;
387+ }
388+
389+ private static int run_common_tests ()
390+ {
391+ string[] args = { "./unity-tool" };
392+ unowned string[] dummy = args;
393+
394+ Test.init (ref dummy);
395+
396+ // checks that lens emits finished signal for every search type
397+ // (and both empty and non-empty searches)
398+ Test.add_data_func ("/Integration/LensTest/DefaultSearch/Empty", () =>
399+ {
400+ var ml = new MainLoop ();
401+ call_lens_search ("", 0, () => { ml.quit (); });
402+ if (!run_with_timeout (ml, 20000)) warn ("Lens didn't respond");
403+ });
404+
405+ Test.add_data_func ("/Integration/LensTest/DefaultSearch/NonEmpty", () =>
406+ {
407+ var ml = new MainLoop ();
408+ call_lens_search ("a", 0, () => { ml.quit (); });
409+ if (!run_with_timeout (ml, 20000)) warn ("Lens didn't respond");
410+ });
411+
412+ // check also non-empty -> empty search
413+ Test.add_data_func ("/Integration/LensTest/DefaultSearch/Empty2", () =>
414+ {
415+ var ml = new MainLoop ();
416+ call_lens_search ("", 0, () => { ml.quit (); });
417+ if (!run_with_timeout (ml, 20000)) warn ("Lens didn't respond");
418+ });
419+
420+ Test.add_data_func ("/Integration/LensTest/GlobalSearch/Empty", () =>
421+ {
422+ var ml = new MainLoop ();
423+ call_lens_search ("", 1, () => { ml.quit (); });
424+ if (!run_with_timeout (ml, 20000)) warn ("Lens didn't respond");
425+ });
426+
427+ Test.add_data_func ("/Integration/LensTest/GlobalSearch/NonEmpty", () =>
428+ {
429+ var ml = new MainLoop ();
430+ call_lens_search ("a", 1, () => { ml.quit (); });
431+ if (!run_with_timeout (ml, 20000)) warn ("Lens didn't respond");
432+ });
433+
434+ // check also non-empty -> empty search
435+ Test.add_data_func ("/Integration/LensTest/GlobalSearch/Empty2", () =>
436+ {
437+ var ml = new MainLoop ();
438+ call_lens_search ("", 1, () => { ml.quit (); });
439+ if (!run_with_timeout (ml, 20000)) warn ("Lens didn't respond");
440+ });
441+
442+ return Test.run ();
443+ }
444+
445+ private static void call_lens_search (string search_string,
446+ int search_type,
447+ Func<Variant?>? cb = null)
448+ {
449+ var vb = new VariantBuilder (new VariantType ("(sa{sv})"));
450+ vb.add ("s", search_string);
451+ vb.open (new VariantType ("a{sv}"));
452+ vb.close ();
453+
454+ try
455+ {
456+ var bus = Bus.get_sync (BusType.SESSION);
457+
458+ bus.call.begin (Options.lens_dbus_name,
459+ Options.lens_dbus_path,
460+ "com.canonical.Unity.Lens",
461+ search_type == 0 ? "Search" : "GlobalSearch",
462+ vb.end (),
463+ null,
464+ 0,
465+ -1,
466+ null,
467+ (obj, res) =>
468+ {
469+ try
470+ {
471+ var reply = bus.call.end (res);
472+ if (cb != null) cb (reply);
473+ }
474+ catch (Error e)
475+ {
476+ warn ("%s", e.message);
477+ }
478+ });
479+ }
480+ catch (Error e) { warn ("%s", e.message); }
481+ }
482+
483+ private static LensInfo get_lens_info ()
484+ {
485+
486+ var ml = new MainLoop ();
487+ LensInfo info = LensInfo ();
488+
489+ try
490+ {
491+ var bus = Bus.get_sync (BusType.SESSION);
492+ var sig_id = bus.signal_subscribe (null,
493+ "com.canonical.Unity.Lens",
494+ "Changed",
495+ Options.lens_dbus_path,
496+ null,
497+ 0,
498+ (conn, sender, obj_path, ifc_name, sig_name, parameters) =>
499+ {
500+ info = (LensInfo) parameters.get_child_value (0);
501+ ml.quit ();
502+ });
503+ bus.call.begin (Options.lens_dbus_name,
504+ Options.lens_dbus_path,
505+ "com.canonical.Unity.Lens",
506+ "InfoRequest",
507+ null,
508+ null,
509+ 0,
510+ -1,
511+ null,
512+ null);
513+
514+ if (!run_with_timeout (ml, 5000)) warn ("Unable to get LensInfo!");
515+
516+ bus.signal_unsubscribe (sig_id);
517+ }
518+ catch (Error e) { warn ("%s", e.message); }
519+
520+ return info;
521+ }
522+
523+ private void sync_model (Dee.SharedModel model)
524+ {
525+ while (!model.synchronized)
526+ {
527+ var ml = new MainLoop ();
528+ var sig_id = model.notify["synchronized"].connect (
529+ () => { ml.quit (); }
530+ );
531+ ml.run ();
532+ SignalHandler.disconnect (model, sig_id);
533+ }
534+ }
535+
536+ private void dump_results_model (Dee.Model model)
537+ {
538+ var iter = model.get_first_iter ();
539+ var last_iter = model.get_last_iter ();
540+
541+ while (iter != last_iter)
542+ {
543+ var row = model.get_row (iter);
544+ print ("%s\t%s\t%u\t%s\t%s\t%s\t%s\n",
545+ row[0].get_string (),
546+ row[1].get_string (),
547+ row[2].get_uint32 (),
548+ row[3].get_string (),
549+ row[4].get_string (),
550+ row[5].get_string (),
551+ row[6].get_string ()
552+ );
553+
554+ iter = model.next (iter);
555+ }
556+ }
557+
558+ private void dump_filters_model (Dee.Model model)
559+ {
560+ var iter = model.get_first_iter ();
561+ var last_iter = model.get_last_iter ();
562+
563+ while (iter != last_iter)
564+ {
565+ var row = model.get_row (iter);
566+ print ("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
567+ row[0].get_string (),
568+ row[1].get_string (),
569+ row[2].get_string (),
570+ row[3].get_string (),
571+ row[4].print (true),
572+ row[5].get_boolean ().to_string (),
573+ row[6].get_boolean ().to_string (),
574+ row[7].get_boolean ().to_string ()
575+ );
576+
577+ iter = model.next (iter);
578+ }
579+ }
580+}

Subscribers

People subscribed via source and target branches