session-migration-0.2.1/0000775000000000000000000000000012263503651012057 5ustar session-migration-0.2.1/CMakeLists.txt0000664000000000000000000000251612263503632014622 0ustar cmake_minimum_required (VERSION 2.8) project(user-migration) find_package (PkgConfig) pkg_check_modules (CACHED_SESSIONMIGRATION_DEPS REQUIRED "glib-2.0;gio-2.0") add_subdirectory(data) add_subdirectory(src) file(COPY debhelper tests DESTINATION ${PROJECT_BINARY_DIR}) add_custom_target(check COMMAND nosetests3 DEPENDS session-migration) macro(add_manpages target manpages translations) foreach(man ${manpages}) string(LENGTH ${man} manpage_length) math(EXPR manpage_length ${manpage_length}-1) string(SUBSTRING ${man} ${manpage_length} 1 section) install(FILES ${man} DESTINATION share/man/man${section}) if (USE_NLS) foreach(translation ${translations}) set(transdir ${CMAKE_CURRENT_BINARY_DIR}/${translation}) add_po4a(man ${man} po/${translation}.po ${transdir}/${man} "") install(FILES ${transdir}/${man} DESTINATION share/man/${translation}/man${section}) set(files ${files} ${transdir}/${man}) endforeach(translation ${translations}) endif(USE_NLS) endforeach(man ${manpages}) add_custom_target(${target} ALL DEPENDS ${files}) endmacro(add_manpages target manpages translations) add_manpages(doc-rawman session-migration.1 "en;") session-migration-0.2.1/debhelper/0000775000000000000000000000000012263503651014011 5ustar session-migration-0.2.1/debhelper/dh_migrations0000775000000000000000000000352312263224526016572 0ustar #!/usr/bin/perl -w =head1 NAME dh_migrations - install session migration files into package build directories =cut use strict; use File::Find; use Debian::Debhelper::Dh_Lib; =head1 SYNOPSIS B [S>] [B<-n>] =head1 DESCRIPTION B is a debhelper program that is responsible for installing B files used by session-migratoin helper into package build directories and add a dependency of the package on session-migration. This helper will add in ${misc:Depends} the session-migration tool as a dependency of the targeted package. Use the --with migrations sequence for debhelper 8+. =head1 FILES =over 4 =item debian/I.migrations List the files and migration scripts to install into each package. The format is a set of lines, where each line lists a script file to install into into usr/share/session-migration/scripts in the package build directory. The name of the files (or directories) to install should be given relative to the current directory. =back =head1 OPTIONS =cut init(); foreach my $package (@{$dh{DOPACKAGES}}) { my $tmp=tmpdir($package); my $file=pkgfile($package,"migrations"); my @scripts; @scripts=filearray($file, ".") if $file; if (($package eq $dh{FIRSTPACKAGE} || $dh{PARAMS_ALL}) && @ARGV) { push @scripts, @ARGV; } foreach my $script (@scripts) { if (! -d "$tmp/usr/share/session-migration/scripts") { doit("install","-d","$tmp/usr/share/session-migration/scripts"); } doit("install","-p","-m755",$script,"$tmp/usr/share/session-migration/scripts"); } if (@scripts) { addsubstvar($package, "misc:Depends", "session-migration"); } } =head1 SEE ALSO L L =head1 AUTHOR Didier Roche Copyright (C) 2012 Canonical Ltd., licensed under the GNU GPL v3 or later. =cut session-migration-0.2.1/debhelper/migrations.pm0000664000000000000000000000017412263224526016526 0ustar #!/usr/bin/perl use warnings; use strict; use Debian::Debhelper::Dh_Lib; insert_after("dh_install", "dh_migrations"); 1; session-migration-0.2.1/COPYING0000664000000000000000000001674312263224526013126 0ustar GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. session-migration-0.2.1/session-migration.10000664000000000000000000000274612263224526015625 0ustar .TH session-migration "1" "12 July 2012" "" "Linux User's Manual" .SH NAME session-migration \- Migrate in user session settings. .SH SYNOPSIS .B session-migration .RI [ options ] .br .SH DESCRIPTION The \fBsession-migration\fP program can be used to migrate in-session user data when a program is evolving its configuration, or needing to have files moved and so on. This program is generally autostarted at the very beginning of the session and integrates caching capabilities. The general usage is to ship an executable file in XDG_DATA_DIR/session-migration/scripts. This one will be executed the next time the user log into the session. It will executes all executable files there in ascii order, one after another. Even if the program tries to cache some timestamp and scripts to not be launched twiced (they are skipped on subsequent runs), the executables file should be idempotent. .SH OPTIONS .IP \fB\-\-dry-run\fP This parameter enables to run the session-migration tool without really launching the scripts and marking them are migrated. It's generally used with --verbose for debugging purposes. .IP \fB\-\-file\fP\ \fIfilename\fP This parameter, followed by a path or filename, tells sesions-migration to only consider that script file and ignoring XDG_DATA_DIR. It won't log as well this special file as being migrated. .IP \fB\-\-help\fP Display a quick command help. .IP \fB\-\-verbose\fP Show a lot of verbose information. It can be used by the user to debug configuration issues; session-migration-0.2.1/debian/0000775000000000000000000000000012263503651013301 5ustar session-migration-0.2.1/debian/session-migration.install0000664000000000000000000000013012263227444020340 0ustar usr/bin/session-migration usr/share/upstart/sessions usr/share/man/*/session-migration* session-migration-0.2.1/debian/compat0000664000000000000000000000000212263224526014500 0ustar 9 session-migration-0.2.1/debian/rules0000775000000000000000000000063612263224526014367 0ustar #!/usr/bin/make -f # -*- makefile -*- # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 VERSION=$(shell dpkg-parsechangelog | sed -n '/^Version:/ { s/^Version: //; p}') %: dh $@ override_dh_install: dh_install --fail-missing mkdir -p debian/dh-migrations/usr/share/man/man1/ pod2man -c Debhelper -r "$(VERSION)" debhelper/dh_migrations debian/dh-migrations/usr/share/man/man1/dh_migrations.1 session-migration-0.2.1/debian/dh-migrations.install0000664000000000000000000000014312263224526017435 0ustar debhelper/dh_migrations usr/bin debhelper/migrations.pm usr/share/perl5/Debian/Debhelper/Sequence/ session-migration-0.2.1/debian/source/0000775000000000000000000000000012263503651014601 5ustar session-migration-0.2.1/debian/source/format0000664000000000000000000000001512263224526016011 0ustar 3.0 (native) session-migration-0.2.1/debian/control0000664000000000000000000000206612263224526014711 0ustar Source: session-migration Section: misc Priority: extra Maintainer: Didier Roche Build-Depends: debhelper (>= 9.0.0), cmake (>= 2.8), libglib2.0-dev, python3, python3-nose, Standards-Version: 3.9.3 Homepage: https://launchpad.net/session-migration Vcs-Bzr: http://code.launchpad.net/~ubuntu-desktop/session-migration/trunk Package: session-migration Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, Description: Tool to migrate in user session settings This tool is used to migrate in session user data when a program is evolving its configuration, or needing to have files moved and so on. . This program is generally autostarted at the very beginning of the session and integrates caching capability. Package: dh-migrations Architecture: all Depends: ${misc:Depends}, debhelper, Description: debhelper extension for session-migration support This package provides a debhelper extension to perform session migration operations on the installed packaged. session-migration-0.2.1/debian/copyright0000664000000000000000000000163012263224526015235 0ustar Format: http://dep.debian.net/deps/dep5 Upstream-Name: session-migration Source: https://launchpad.net/session-migration Files: * Copyright: 2012 Canonical License: LGPL-3+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3, or any latter version. . This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this program. If not, see . On Debian systems, the complete text of the GNU Lesser General Public License version 3 can be found in "/usr/share/common-licenses/LGPL-3". session-migration-0.2.1/debian/changelog0000664000000000000000000000135412263503636015161 0ustar session-migration (0.2.1) trusty; urgency=low [ Didier Roche ] * add more info to the manpage [ Iain Lane ] * Add an upstart user session script, so we can run session-migration under more types of session. -- Iain Lane Thu, 09 Jan 2014 11:27:26 +0000 session-migration (0.2) quantal; urgency=low * dh_migrations adds a dependency using ${misc:Depends} on the packages using a migration * add a second binary packages for test and making additional checks * fix a typo in the man -- Didier Roche Thu, 19 Jul 2012 16:50:49 +0200 session-migration (0.1) quantal; urgency=low * Initial release -- Didier Roche Thu, 12 Jul 2012 18:41:19 +0200 session-migration-0.2.1/src/0000775000000000000000000000000012263503651012646 5ustar session-migration-0.2.1/src/CMakeLists.txt0000664000000000000000000000044512263224526015412 0ustar set (CFLAGS ${CACHED_SESSIONMIGRATION_DEPS_CFLAGS}) set (LIBS ${CACHED_SESSIONMIGRATION_DEPS_LIBRARIES} ${UNITY_STANDALONE_LADD}) add_definitions (${CFLAGS}) link_libraries (${LIBS}) add_executable (session-migration session-migration.c) install(TARGETS session-migration DESTINATION bin) session-migration-0.2.1/src/session-migration.c0000664000000000000000000002220012263224526016461 0ustar /* * Copyright (C) 2010 Red Hat, Inc. * Copyright (C) 2012 Canonical * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the Licence, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . * * Authors: Matthias Clasen * Didier Roche */ #include #include #include #include #define MIGRATION_FILENAME_BASE "session_migration-" static gboolean verbose = FALSE; static gboolean dry_run = FALSE; static gchar* get_migration_filename () { gchar *full_session_name; gchar *filename; full_session_name = g_strdup_printf ("%s%s", MIGRATION_FILENAME_BASE, g_getenv("DESKTOP_SESSION")); filename = g_build_filename (g_get_user_data_dir (), full_session_name, NULL); g_free(full_session_name); return filename; } static gboolean migrate_from_file (const gchar *script_path) { gchar *stdout = NULL; gchar *stderr = NULL; gint exit_status; GError *error = NULL; if (verbose) g_print ("Executing: %s\n", script_path); if (dry_run) return TRUE; if (!g_spawn_command_line_sync(script_path, &stdout, &stderr, &exit_status, &error) || (exit_status != 0)) { if (error != NULL) g_printerr ("%s\nstdout: %s\nstderr: %s\n", error->message, stdout, stderr); else g_printerr("Exited with an error\nstdout: %s\nstderr: %s\n", stdout, stderr); return FALSE; } return TRUE; } static gboolean migrate_from_dir (const gchar *dirname, time_t stored_mtime, GHashTable *migrated, gboolean *changed) { time_t dir_mtime; struct stat statbuf; GDir *dir; const gchar *name; gchar *filename; GSList *migration_scripts = NULL; GSList *current_script; GError *error; *changed = FALSE; /* If the directory is not newer, exit */ if (stat (dirname, &statbuf) == 0) dir_mtime = statbuf.st_mtime; else { if (verbose) g_print ("Directory '%s' does not exist, nothing to do\n", dirname); return TRUE; } if (dir_mtime <= stored_mtime) { if (verbose) g_print ("Directory '%s' all uptodate, nothing to do\n", dirname); return TRUE; } error = NULL; dir = g_dir_open (dirname, 0, &error); if (dir == NULL) { g_printerr ("Failed to open '%s': %s\n", dirname, error->message); return FALSE; } if (verbose) g_print ("Using '%s' directory\n", dirname); while ((name = g_dir_read_name (dir)) != NULL) { if (g_hash_table_lookup (migrated, name)) { if (verbose) g_print ("File '%s already migrated, skipping\n", name); continue; } migration_scripts = g_slist_insert_sorted(migration_scripts, (gpointer)name, (GCompareFunc)g_strcmp0); } if (migration_scripts != NULL) { current_script = migration_scripts; do { filename = g_build_filename (dirname, current_script->data, NULL); if (migrate_from_file (filename)) { gchar *myname = g_strdup (current_script->data); /* add the file to the migrated list */ g_hash_table_insert (migrated, myname, myname); *changed = TRUE; } g_free (filename); } while ((current_script = g_slist_next(current_script)) != NULL); g_slist_free (migration_scripts); } g_dir_close (dir); return TRUE; } /* get_string_set() and set_string_set() could be GKeyFile API */ static GHashTable * get_string_set (GKeyFile *keyfile, const gchar *group, const gchar *key, GError **error) { GHashTable *migrated; gchar **list; gint i; list = g_key_file_get_string_list (keyfile, group, key, NULL, error); if (list == NULL) return NULL; migrated = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); for (i = 0; list[i]; i++) g_hash_table_insert (migrated, list[i], list[i]); /* The hashtable now owns the strings, so only free the array */ g_free (list); return migrated; } static void set_string_set (GKeyFile *keyfile, const gchar *group, const gchar *key, GHashTable *set) { GHashTableIter iter; GString *list; gpointer item; list = g_string_new (NULL); g_hash_table_iter_init (&iter, set); while (g_hash_table_iter_next (&iter, &item, NULL)) g_string_append_printf (list, "%s;", (const gchar *) item); g_key_file_set_value (keyfile, group, key, list->str); g_string_free (list, TRUE); } static GHashTable * load_state (time_t *mtime) { GHashTable *migrated; GHashTable *tmp; gchar *filename; GKeyFile *keyfile; GError *error; gchar *str; migrated = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); filename = get_migration_filename(); /* ensure file exists */ if (!g_file_test (filename, G_FILE_TEST_EXISTS)) { g_free (filename); return migrated; } error = NULL; keyfile = g_key_file_new(); if (!g_key_file_load_from_file (keyfile, filename, 0, &error)) { g_printerr ("%s: %s\n", filename, error->message); g_error_free (error); g_key_file_free (keyfile); g_free (filename); return migrated; } error = NULL; if ((str = g_key_file_get_string (keyfile, "State", "timestamp", &error)) == NULL) { g_printerr ("%s\n", error->message); g_error_free (error); } else { *mtime = (time_t)g_ascii_strtoll (str, NULL, 0); g_free (str); } error = NULL; if ((tmp = get_string_set (keyfile, "State", "migrated", &error)) == NULL) { g_printerr ("%s\n", error->message); g_error_free (error); } else { g_hash_table_unref (migrated); migrated = tmp; } g_key_file_free (keyfile); g_free (filename); return migrated; } static gboolean save_state (GHashTable *migrated) { gchar *filename; GKeyFile *keyfile; gchar *str; GError *error; gboolean result; /* Make sure the state directory exists */ if (g_mkdir_with_parents (g_get_user_data_dir (), 0755)) { g_printerr ("Failed to create directory %s: %s\n", g_get_user_data_dir (), g_strerror (errno)); return FALSE; } filename = get_migration_filename(); keyfile = g_key_file_new (); str = g_strdup_printf ("%ld", time (NULL)); g_key_file_set_string (keyfile, "State", "timestamp", str); g_free (str); set_string_set (keyfile, "State", "migrated", migrated); str = g_key_file_to_data (keyfile, NULL, NULL); g_key_file_free (keyfile); error = NULL; if (!g_file_set_contents (filename, str, -1, &error)) { g_printerr ("%s\n", error->message); g_error_free (error); result = FALSE; } else result = TRUE; g_free (filename); g_free (str); return result; } int main (int argc, char *argv[]) { time_t stored_mtime = 0; const gchar * const *data_dirs; const gchar *extra_file = NULL; GError *error; GHashTable *migrated; gboolean changed = FALSE; int i; GOptionContext *context;; GOptionEntry entries[] = { { "verbose", 0, 0, G_OPTION_ARG_NONE, &verbose, "show verbose messages", NULL }, { "dry-run", 0, 0, G_OPTION_ARG_NONE, &dry_run, "do not perform any changes", NULL }, { "file", 0, 0, G_OPTION_ARG_STRING, &extra_file, "Force a migration from this file only (no storage of migrated status)", NULL }, { NULL } }; g_type_init(); context = g_option_context_new (""); g_option_context_set_summary (context, "Migrate in user session settings."); g_option_context_add_main_entries (context, entries, NULL); error = NULL; if (!g_option_context_parse (context, &argc, &argv, &error)) { g_printerr ("%s\n", error->message); return 1; } g_option_context_free (context); migrated = load_state (&stored_mtime); if (extra_file) { if (!migrate_from_file (extra_file)) return 1; return 0; } data_dirs = g_get_system_data_dirs (); for (i = 0; data_dirs[i]; i++) { gchar *migration_dir; gboolean changed_in_dir; migration_dir = g_build_filename (data_dirs[i], "session-migration", "scripts", NULL); if (!migrate_from_dir (migration_dir, stored_mtime, migrated, &changed_in_dir)) { g_free (migration_dir); g_hash_table_destroy (migrated); return 1; } changed = changed | changed_in_dir; g_free (migration_dir); } if (changed && !dry_run) { if (!save_state (migrated)) { g_hash_table_destroy (migrated); return 1; } } g_hash_table_destroy (migrated); return 0; } session-migration-0.2.1/tests/0000775000000000000000000000000012263503651013221 5ustar session-migration-0.2.1/tests/debhelper_tests.py0000664000000000000000000000667212263224526016763 0ustar #!/usr/bin/python # Copyright (C) 2012 Canonical # # Authors: # Didier Roche # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation; either version 3 of the License, # or (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . import os import shutil import subprocess import tempfile import unittest class DebhelperTests(unittest.TestCase): def setUp(self): self.srcdir = os.path.abspath(os.path.join('tests', 'data', 'melticecream')) self.workdir = tempfile.mkdtemp() self.pkgdir = os.path.join(self.workdir, 'melticecream') shutil.copytree(self.srcdir, self.pkgdir) env = os.environ.copy() # do not depend on host induced build options env['DEB_BUILD_OPTIONS'] = '' # point to local debhelper sequencer d = os.path.join(self.workdir, 'Debian', 'Debhelper', 'Sequence') os.makedirs(d) shutil.copy('debhelper/migrations.pm', d) env['PERLLIB'] = '%s:%s' % (self.workdir, env.get('PERLLIB', '')) # make dh_migrations available in $PATH shutil.copy('debhelper/dh_migrations', os.path.join(self.workdir, 'dh_migrations')) env['PATH'] = self.workdir + ':' + env.get('PATH', '') self.env = env def tearDown(self): shutil.rmtree(self.workdir) def buildpackage(self, env): '''helper to build a package''' build = subprocess.Popen(['dpkg-buildpackage', '-us', '-uc', '-b'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=self.pkgdir, env=env) return build.communicate()[0].decode() def test_build_with_migrations(self): '''build the package with migration argument''' stdout = self.buildpackage(self.env) self.assertTrue("dh_migrations" in stdout) # check the scripts are installed and executable scripts_path = os.path.join(self.pkgdir, 'debian/vanilla/usr/share/session-migration/scripts') self.assertTrue(os.path.isdir(scripts_path)) self.assertTrue(os.path.isfile(os.path.join(scripts_path, "script1.sh"))) self.assertTrue(os.path.isfile(os.path.join(scripts_path, "script2.sh"))) self.assertTrue(os.access(os.path.join(scripts_path, "script1.sh"), os.X_OK)) self.assertTrue(os.access(os.path.join(scripts_path, "script2.sh"), os.X_OK)) # check the dep was added: with open(os.path.join(self.pkgdir, 'debian/vanilla/DEBIAN/control')) as f: self.assertEquals(f.read().count("session-migration"), 1) def test_build_with_missing_script(self): '''ensure assert when there is a typo in the script path or doesn't exist''' os.remove(os.path.join(self.pkgdir, "script1.sh")) stdout = self.buildpackage(self.env) self.assertTrue("dh_migrations: install -p -m755 script1.sh debian/vanilla/usr/share/session-migration/scripts returned exit code 1" in stdout) if __name__ == '__main__': unittest.main() session-migration-0.2.1/tests/migration_tests.py0000664000000000000000000002672412263224526017022 0ustar #!/usr/bin/python # Copyright (C) 2012 Canonical # # Authors: # Didier Roche # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation; either version 3 of the License, # or (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . import configparser import json import os import re import shutil import stat import subprocess import tempfile import time import unittest class MigrationTests(unittest.TestCase): def setUp(self): self.config = configparser.RawConfigParser() self.tmpdir = None self.output_files = None self.setup_env() def tearDown(self): self.clean_env() def clean_env(self): '''Clean setup files if present''' if self.tmpdir: try: shutil.rmtree(self.tmpdir) except OSError: pass self.tmpdir = None if self.output_files: for output_file in self.output_files: try: os.remove(output_file) except OSError: pass self.output_files = None def setup_env(self, test_name='main', systemtemp=False): '''Setup the env for a particular test domain''' # if we already called setup_env, clean it first self.clean_env() self.tmpdir = tempfile.mkdtemp() os.environ['DESKTOP_SESSION'] = 'migtestsession' home_migration_dir = os.path.join(self.tmpdir, 'home') self.migration_home_file = os.path.join(home_migration_dir, 'session_migration-migtestsession') os.environ['XDG_DATA_HOME'] = home_migration_dir if systemtemp: system_data_path = os.path.join(self.tmpdir, 'system') os.environ['XDG_DATA_DIRS'] = system_data_path else: system_data_path = os.path.abspath(os.path.join('tests', 'data', test_name)) os.environ['XDG_DATA_DIRS'] = system_data_path # loading the expected result with open(os.path.join(system_data_path, 'output_files')) as f: self.output_files = json.load(f) self.script_path = os.path.join(system_data_path, 'session-migration', 'scripts') def run_migration(self, command=None, verbose=True, additional_params=None): '''helper to run migration tool''' if not command: command = ['./src/session-migration'] if verbose: command.append('--verbose') if additional_params: command.extend(additional_params) p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() return (stdout.decode('UTF-8'), stderr.decode('UTF-8')) def test_no_output_default(self): '''Test that by default, there is no output''' self.setup_env(systemtemp=True) (stdout, stderr) = self.run_migration(verbose=False) self.assertEqual(stdout, '') self.assertEqual(stderr, '') # nothing should be created self.assertFalse(os.path.isfile(self.migration_home_file)) def test_detect_dirs_doesn_t_exist(self): '''Test that some system dirs doesn't exist and nothing is done (but that multiple directories are detected)''' self.setup_env(systemtemp=True) os.environ['XDG_DATA_DIRS'] = '{}:{}'.format(os.environ['XDG_DATA_DIRS'], os.path.join(self.tmpdir, 'system2')) self.home_migration_dir = '/tmp/migrationstests' (stdout, stderr) = self.run_migration() self.assertEqual(stdout, "Directory '{}' does not exist, nothing to do\nDirectory '{}' does not exist, nothing to do\n".format( os.path.join(self.tmpdir, 'system', 'session-migration/scripts'), os.path.join(self.tmpdir, 'system2', 'session-migration/scripts'))) self.assertEqual(stderr, '') # nothing should be created self.assertFalse(os.path.isfile(self.migration_home_file)) def test_migration(self): '''Test that the migration happens as expected''' before_run_timestamp = int(time.time()) time.sleep(1) # for the timing test (stdout, stderr) = (self.run_migration()) # ensure scripts are executed in the right order self.assertEqual(stdout, "Using '{}' directory\nExecuting: {}\nExecuting: {}\nExecuting: {}\nExecuting: {}\n".format( self.script_path, os.path.join(self.script_path, '01_test.sh'), os.path.join(self.script_path, '02_test.sh'), os.path.join(self.script_path, '08_test.sh'), os.path.join(self.script_path, '10_test.sh'))) # ensure scripts 8 is not successfully working self.assertEqual(stderr, 'Failed to execute child process "{}" (Permission denied)\nstdout: (null)\nstderr: (null)\n'.format(os.path.join(self.script_path, '08_test.sh'))) # ensure that the script are indeed have been run/not run and created the expected touched file for output_file in self.output_files: if self.output_files[output_file]: self.assertTrue(os.path.isfile(output_file)) else: self.assertFalse(os.path.isfile(output_file)) # ensure that the scripts are marked as done and exit under right session name (encoded in migration_home_file) self.config.read(self.migration_home_file) time.sleep(1) # for the timing test # check the timestamp after_run_timestamp = int(time.time()) self.assertTrue(self.config.getint('State', 'timestamp') < after_run_timestamp) self.assertTrue(self.config.getint('State', 'timestamp') > before_run_timestamp) # ensure that the scripts are stamped as migrated self.assertEqual(self.config.get('State', 'migrated'), '01_test.sh;02_test.sh;10_test.sh;') def test_migration_with_dry_run(self): '''Test that the migration claimed to happen as expected in dry run mode''' (stdout, stderr) = (self.run_migration(additional_params=["--dry-run"])) # ensure scripts are executed in the right order self.assertEqual(stdout, "Using '{}' directory\nExecuting: {}\nExecuting: {}\nExecuting: {}\nExecuting: {}\n".format( self.script_path, os.path.join(self.script_path, '01_test.sh'), os.path.join(self.script_path, '02_test.sh'), os.path.join(self.script_path, '08_test.sh'), os.path.join(self.script_path, '10_test.sh'))) # ensure scripts 8 is not executed (and so no failure) self.assertEqual(stderr, '') # ensure that the script have indeed not be run for output_file in self.output_files: self.assertFalse(os.path.isfile(output_file)) # ensure that the scripts are not marked as done self.assertFalse(os.path.isfile(self.migration_home_file)) def test_subsequent_runs_no_effect(self): '''Test that subsequent runs doesn't have any effect''' (stdout, stderr) = (self.run_migration()) self.assertNotEqual(stdout, '') self.assertNotEqual(stderr, '') self.assertTrue(os.path.isfile(self.migration_home_file)) with open(self.migration_home_file) as f: home_file = f.read() (stdout, stderr) = (self.run_migration()) self.assertEqual(stdout, "Directory '{}' all uptodate, nothing to do\n".format(self.script_path)) self.assertEqual(stderr, '') with open(self.migration_home_file) as f: second_home_file = f.read() self.assertEqual(home_file, second_home_file) def test_subsequent_runs_with_new_script(self): '''Test subsequent runs with a new script''' (stdout, stderr) = (self.run_migration()) # remove all migration content (to ensure later that scripts are not reexecuted) for output_file in self.output_files: if self.output_files[output_file]: os.remove(output_file) # add a script to be runned in the same directory time.sleep(1) # ensure that the directory is touched after the first run new_script = os.path.join(self.script_path, '08_testexecute.sh') shutil.copy(os.path.join(self.script_path, '08_test.sh'), new_script) os.chmod(new_script, stat.S_IREAD + stat.S_IEXEC) before_second_run = int(time.time()) time.sleep(1) # for the timing test (stdout, stderr) = (self.run_migration()) time.sleep(1) # for the timing test after_second_run = int(time.time()) self.config.read(self.migration_home_file) # remove the script os.remove(new_script) # check the debug output self.assertTrue(re.match("Using '{}' directory\nFile '{files}_test.sh already migrated, skipping\nFile '{files}_test.sh already migrated, skipping\n" "File '{files}_test.sh already migrated, skipping\nExecuting: {}\nExecuting: {}\n".format(self.script_path, os.path.join(self.script_path, '08_test.sh'), os.path.join(self.script_path, '08_testexecute.sh'), files='(01|02|10)'), stdout)) self.assertEqual(stderr, 'Failed to execute child process "{}" (Permission denied)\nstdout: (null)\nstderr: (null)\n'.format(os.path.join(self.script_path, '08_test.sh'))) # inverse the condition to ensure only the latest copied script has been executed for output_file in self.output_files: if not self.output_files[output_file]: self.assertTrue(os.path.isfile(output_file)) else: self.assertFalse(os.path.isfile(output_file)) # check that the timestamp is from the second run self.assertTrue(self.config.getint('State', 'timestamp') < after_second_run) self.assertTrue(self.config.getint('State', 'timestamp') > before_second_run) # ensure that the new script is stamped as migrated self.assertEqual(self.config.get('State', 'migrated'), '01_test.sh;02_test.sh;10_test.sh;08_testexecute.sh;') def test_run_only_one_script(self): '''Test that you can run one script only''' (stdout, stderr) = self.run_migration(additional_params=["--file={}".format(os.path.join(self.script_path, '10_test.sh'))]) # check the output that only this file was ran self.assertEqual(stdout, "Executing: {}\n".format(os.path.join(self.script_path, '10_test.sh'))) self.assertEqual(stderr, '') # check that only this touched file has been created for output_file in self.output_files: if '10' in output_file: self.assertTrue(os.path.isfile(output_file)) else: self.assertFalse(os.path.isfile(output_file)) # nothing should be logged self.assertFalse(os.path.isfile(self.migration_home_file)) if __name__ == '__main__': unittest.main() session-migration-0.2.1/tests/data/0000775000000000000000000000000012263503651014132 5ustar session-migration-0.2.1/tests/data/main/0000775000000000000000000000000012263503651015056 5ustar session-migration-0.2.1/tests/data/main/session-migration/0000775000000000000000000000000012263503651020530 5ustar session-migration-0.2.1/tests/data/main/session-migration/scripts/0000775000000000000000000000000012263503651022217 5ustar session-migration-0.2.1/tests/data/main/session-migration/scripts/01_test.sh0000775000000000000000000000005112263224526024032 0ustar #!/bin/sh touch /tmp/usermigrationtest01 session-migration-0.2.1/tests/data/main/session-migration/scripts/10_test.sh0000775000000000000000000000005112263224526024032 0ustar #!/bin/sh touch /tmp/usermigrationtest10 session-migration-0.2.1/tests/data/main/session-migration/scripts/02_test.sh0000775000000000000000000000005112263224526024033 0ustar #!/bin/sh touch /tmp/usermigrationtest02 session-migration-0.2.1/tests/data/main/session-migration/scripts/08_test.sh0000664000000000000000000000005112263224526024036 0ustar #!/bin/sh touch /tmp/usermigrationtest08 session-migration-0.2.1/tests/data/main/output_files0000664000000000000000000000021112263224526017516 0ustar {"/tmp/usermigrationtest08": false, "/tmp/usermigrationtest01": true, "/tmp/usermigrationtest02": true, "/tmp/usermigrationtest10": true}session-migration-0.2.1/tests/data/melticecream/0000775000000000000000000000000012263503651016564 5ustar session-migration-0.2.1/tests/data/melticecream/script1.sh0000664000000000000000000000000412263224526020500 0ustar foo session-migration-0.2.1/tests/data/melticecream/script2.sh0000664000000000000000000000000412263224526020501 0ustar bar session-migration-0.2.1/tests/data/melticecream/debian/0000775000000000000000000000000012263503651020006 5ustar session-migration-0.2.1/tests/data/melticecream/debian/vanilla.migrations0000664000000000000000000000002612263224526023531 0ustar script1.sh script2.sh session-migration-0.2.1/tests/data/melticecream/debian/compat0000664000000000000000000000000212263224526021205 0ustar 9 session-migration-0.2.1/tests/data/melticecream/debian/rules0000775000000000000000000000007312263224526021067 0ustar #!/usr/bin/make -f %: dh $@ --with migrations --parallel session-migration-0.2.1/tests/data/melticecream/debian/control0000664000000000000000000000052312263224526021412 0ustar Source: melticecream Section: utils Priority: extra Maintainer: Joe User Build-Depends: debhelper (>= 9) Standards-Version: 3.9.3 Package: vanilla Architecture: all Depends: ${misc:Depends} Description: vanilla vanilla Package: nochocolate Architecture: all Depends: ${misc:Depends} Description: chocolate chocolate session-migration-0.2.1/tests/data/melticecream/debian/copyright0000664000000000000000000000000412263224526021734 0ustar GPL session-migration-0.2.1/tests/data/melticecream/debian/changelog0000664000000000000000000000025212263224526021660 0ustar melticecream (1) testsuite; urgency=low * Initial but late release… icecream has melt down! -- Didier Roche Fri, 13 Jul 2012 08:34:22 +0200 session-migration-0.2.1/data/0000775000000000000000000000000012263503651012770 5ustar session-migration-0.2.1/data/CMakeLists.txt0000664000000000000000000000011212263227352015523 0ustar install (FILES session-migration.conf DESTINATION share/upstart/sessions) session-migration-0.2.1/data/session-migration.conf0000664000000000000000000000023712263234640017312 0ustar description "Session Migration" author "Iain Lane " start on (started dbus and starting xsession-init) task exec session-migration