-
 KDE-Apps.org Applications for the KDE-Desktop 
 GTK-Apps.org Applications using the GTK Toolkit 
 GnomeFiles.org Applications for GNOME 
 MeeGo-Central.org Applications for MeeGo 
 CLI-Apps.org Command Line Applications 
 Qt-Apps.org Free Qt Applications 
 Qt-Prop.org Proprietary Qt Applications 
 Maemo-Apps.org Applications for the Maemo Plattform 
 Java-Apps.org Free Java Applications 
 eyeOS-Apps.org Free eyeOS Applications 
 Wine-Apps.org Wine Applications 
 Server-Apps.org Server Applications 
 apps.ownCloud.com ownCloud Applications 
--
-
 KDE-Look.org Artwork for the KDE-Desktop 
 GNOME-Look.org Artwork for the GNOME-Desktop 
 Xfce-Look.org Artwork for the Xfce-Desktop 
 Box-Look.org Artwork for your Windowmanager 
 E17-Stuff.org Artwork for Enlightenment 
 Beryl-Themes.org Artwork for the Beryl Windowmanager 
 Compiz-Themes.org Artwork for the Compiz Windowmanager 
 EDE-Look.org Themes for your EDE Desktop 
--
-
 Debian-Art.org Stuff for Debian 
 Gentoo-Art.org Artwork for Gentoo Linux 
 SUSE-Art.org Artwork for openSUSE 
 Ubuntu-Art.org Artwork for Ubuntu 
 Kubuntu-Art.org Artwork for Kubuntu 
 LinuxMint-Art.org Artwork for Linux Mint 
 Arch-Stuff.org Art And Stuff for Arch Linux 
 Frugalware-Art.org Themes for Frugalware 
 Fedora-Art.org Artwork for Fedora Linux 
 Mandriva-Art.org Artwork for Mandriva Linux 
--
-
 KDE-Files.org Files for KDE Applications 
 OpenTemplate.org Documents for OpenOffice.org
 GIMPStuff.org Files for GIMP
 InkscapeStuff.org Files for Inkscape
 ScribusStuff.org Files for Scribus
 BlenderStuff.org Textures and Objects for Blender
 VLC-Addons.org Themes and Extensions for VLC
--
-
 KDE-Help.org Support for your KDE Desktop 
 GNOME-Help.org Support for your GNOME Desktop 
 Xfce-Help.org Support for your Xfce Desktop 
--
openDesktop.orgopenDesktop.org:   Applications   Artwork   Linux Distributions   Documents    LinuxDaily.com    Linux42.org    OpenSkillz.com   
 
Artwork
News
Groups
Knowledge
Events
Forum
People
Jobs
Register
Login

-
- News . 
0
votes
click to vote up

Dylan McCall: GNOME Break Timer: Week 8


Published Aug 14 2013 via RSS

Seriously regretting my boring choice of titles for these blog posts, but it’s too late to change it now.

Why, hello there! The last two weeks haven’t been the brilliantest for my work on GNOME Break Timer – partly because all my other unrelated projects, which I’ve been mostly ignoring in favour of Break Timer, have suddenly flared up and demanded attention – but I still got some nice stuff done. And I passed my statistics course and almost finished a cool charity website. (More on that soon, I hope?).

Most importantly, I decided that it’s time to add some tests. I added a test folder to the build system, and after bracing myself for a nightmare I was really impressed with how easy it was to get going. Further evidence that my fear of tests is entirely irrational.

With all the GNOME project automake stuff set up, I just needed to build a test runner using GLib’s test framework and list it in the TEST_PROGS variable. make check is figured out all on its own. So, that was lovely!

While everyone was off having fun at GUADEC, I fooled around writing unit tests. Of course, I did run into some trickiness: most of my time was spent making the application more testable (cursing Automake some more, putting everything in noinst_LTLIBRARIES), and fixing bugs that I encountered in the process of writing unit tests. This is all for a good cause, though: I am feeling more and more confident that the bus factor for this project can increase beyond 1.

Of course, I didn’t write unit tests for everything. That would be lovely, but it could also take quite a long time. (Quicker now, of course, since all the kinks have been worked out). Instead, I focused on parts of the application that I have broken by accident in the past: monitoring activity, and triggering breaks. The application uses many global things like system time, as well as timers and timeouts. Those can all be rather troublesome to test, unfortunately, but I found my way around them. I created a custom g_get_real_time function that will either return the actual time or a time set by the test suite, so we can rigorously test how certain objects behave as the time changes.

Most of this is quite boring, but I’m happy with it anyway. I wasn’t thrilled with glib’s syntax for writing a test suite – I’m used to wrapping these things in objects – so I borrowed the TestSuite and TestCase classes from libgee’s test suite, adding some extra twists.

Here is tests.vala, which is used by all of the test runners:

// GLib's TestSuite and TestCase are compact classes, so we wrap them in real GLib.Objects for convenience
// This base code is partly borrowed from libgee's test suite, at https://git.gnome.org/browse/libgee

public abstract class SimpleTestSuite : Object {
	private GLib.TestSuite g_test_suite;
	private Adaptor[] adaptors = new Adaptor[0];

	private class Adaptor {
		private SimpleTestSuite test_suite;
		private SimpleTestCase test;

		public Adaptor(SimpleTestSuite test_suite, owned SimpleTestCase test) {
			this.test_suite = test_suite;
			this.test = (owned)test;
		}

		private string get_short_name() {
			string base_name = this.test_suite.get_name();
			string test_full_name = this.test.get_name();
			if (test_full_name.has_prefix(base_name)) {
				return test_full_name.splice(0, base_name.length);
			} else {
				return test_full_name;
			}
		}

		private void setup(void *fixture) {
			this.test_suite.setup();
		}

		private void run(void *fixture) {
			this.test.run(this.test_suite);
		}

		private void teardown(void *fixture) {
			this.test_suite.teardown();
		}

		public GLib.TestCase get_g_test_case() {
			return new GLib.TestCase(
				this.get_short_name(),
				(TestFixtureFunc)this.setup,
				(TestFixtureFunc)this.run,
				(TestFixtureFunc)this.teardown
			);
		}
	}

	public SimpleTestSuite() {
		var name = this.get_name();
		this.g_test_suite = new GLib.TestSuite(name);
	}

	public void add_to(GLib.TestSuite parent) {
		parent.add_suite(this.g_test_suite);
	}

	public GLib.TestSuite get_g_test_suite() {
		return this.g_test_suite;
	}

	public string get_name() {
		return this.get_type().name();
	}

	public void add_test(owned SimpleTestCase test) {
		var adaptor = new Adaptor(this, (owned)test);
		this.adaptors += adaptor;
		this.g_test_suite.add(adaptor.get_g_test_case());
	}

	public virtual void setup() {
	}

	public virtual void teardown() {
	}
}

public interface SimpleTestCase : Object {
	public abstract void run(T context);

	public void add_to(SimpleTestSuite test_suite) {
		test_suite.add_test(this);
	}

	public string get_name() {
		return this.get_type().name();
	}
}


class TestRunner : Object {
	private GLib.TestSuite root_suite;

	private File tmp_dir;
	const string SCHEMA_FILE_NAME = "org.gnome.break-timer.gschema.xml";

	public TestRunner(ref unowned string[] args, GLib.TestSuite? root_suite = null) {
		GLib.Test.init(ref args);
		if (root_suite == null) {
			this.root_suite = GLib.TestSuite.get_root();
		} else {
			this.root_suite = root_suite;
		}
	}

	public void add(SimpleTestSuite suite) {
		suite.add_to(this.root_suite);
	}

	public virtual void global_setup() {
		try {
			var tmp_path = DirUtils.make_tmp("gnome-break-timer-test-XXXXXX");
			tmp_dir = File.new_for_path(tmp_path);
		} catch (Error e) {
			GLib.warning("Error creating temporary directory for test files: %s".printf(e.message));
		}

		string target_data_path = Path.build_filename(tmp_dir.get_path(), "share");
		string target_schema_path = Path.build_filename(tmp_dir.get_path(), "share", "glib-2.0", "schemas");

		Environment.set_variable("GSETTINGS_BACKEND", "memory", true);

		var original_data_dirs = Environment.get_variable("XDG_DATA_DIRS");
		Environment.set_variable("XDG_DATA_DIRS", "%s:%s".printf(target_data_path, original_data_dirs), true);

		File source_schema_file = File.new_for_path(
			Path.build_filename(get_top_builddir(), "data", SCHEMA_FILE_NAME)
		);

		File target_schema_dir = File.new_for_path(target_schema_path);
		try {
			target_schema_dir.make_directory_with_parents();
		} catch (Error e) {
			GLib.warning("Error creating directory for schema files: %s", e.message);
		}

		File target_schema_file = File.new_for_path(
			Path.build_filename(target_schema_dir.get_path(), SCHEMA_FILE_NAME)
		);

		try {
			source_schema_file.copy(target_schema_file, FileCopyFlags.OVERWRITE);
		} catch (Error e) {
			GLib.warning("Error copying schema file: %s", e.message);
		}

		int compile_schemas_result = Posix.system("glib-compile-schemas %s".printf(target_schema_path));
		if (compile_schemas_result != 0) {
			GLib.warning("Could not compile schemas in %s", target_schema_path);
		}
	}

	public virtual void global_teardown() {
		if (tmp_dir != null) {
			var tmp_dir_path = tmp_dir.get_path();
			int delete_tmp_result = Posix.system("rm -rf %s".printf(tmp_dir_path));
			if (delete_tmp_result != 0) {
				GLib.warning("Could not delete temporary files in %s", tmp_dir_path);
			}
		}
	}

	public int run() {
		this.global_setup();
		GLib.Test.run();
		this.global_teardown();
		return 0;
	}

	private static string get_top_builddir() {
		var builddir = Environment.get_variable("top_builddir");
		if (builddir == null) builddir = "..";
		return builddir;
	}
}

And here’s a really simple test suite and test runner:

public class test_Example : SimpleTestSuite {
	public string? foo;

	public test_Example() {
		new test_example_foo_is_bar().add_to(this);
	}

	public override void setup() {
		this.foo = "bar";
	}
}

class test_example_foo_is_bar : Object, SimpleTestCase {
	public void run(test_Example context) {
		assert(context.foo == "bar");
	}
}

public static int main(string[] args) {
	var runner = new TestRunner(ref args);
	runner.add(new test_Example());
	return runner.run();
}

Of course, you might note that this still isn’t thread-safe since we’re passing the same test_Example instance as the parameter for all of our SimpleTestCases, and the syntax is slightly unusual, but I’m quite fond of the extra brevity. One nice bit is this figures out the name of each test based on GObject type information, so we never need to write it explicitly. The test runner ultimately says that a test named “/test_Example/test_example_foo_is_bar” has passed, and it can deal with all sorts of stuff well away from the test code. It’s worked well so far, anyway.

So, that’s about it for the last two weeks. I also submitted an art request for some new icons, and I’m going to try following up on that where I can. This is definitely in the “polish” phase – just with a lot yet to be polished.



BackRead original postSend to a friend

Add comment

Add comment
Show all posts




-
 
 
 Who we are
Contact
More about us
Frequently Asked Questions
Register
Twitter
Blog
Explore
Artwork
Jobs
Knowledge
Events
People
Updates on identi.ca
Updates on Twitter
Facebook App
Content RSS   
Events RSS   

Participate
Groups
Forum
Add Artwork
Public API
About GNOME-Look.org
Legal Notice
Spreadshirt Shop
CafePress Shop
Advertising
Sponsor us
Report Abuse
 

Copyright 2003-2014 GNOME-Look.org Team  
All rights reserved. GNOME-Look.org is not liable for any content or goods on this site.
All contributors are responsible for the lawfulness of their uploads.
GNOME and the foot logo are trademarks of the GNOME Foundation.