Add some api for handling SQLite3 migrations
Also port the SqliteHistoryAdapter to the new api.
Testing Done:
Ran the unit tests under valgrind and checked out code coverage which is really just missing error handling I can't test.
Bugs closed: PIDGIN-17695
Reviewed at https://reviews.imfreedom.org/r/1906/
--- a/libpurple/meson.build Mon Oct 10 00:27:32 2022 -0500
+++ b/libpurple/meson.build Mon Oct 10 00:38:48 2022 -0500
@@ -85,6 +85,7 @@
'purpleprotocolwhiteboard.c',
'purplesqlitehistoryadapter.c',
@@ -191,6 +192,7 @@
'purpleprotocolwhiteboard.h',
'purplesqlitehistoryadapter.h',
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/purplesqlite3.c Mon Oct 10 00:38:48 2022 -0500
@@ -0,0 +1,205 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 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 <https://www.gnu.org/licenses/>. +#include "purplesqlite3.h" +/****************************************************************************** + *****************************************************************************/ +purple_sqlite3_run_migration(sqlite3 *db, int version, const char *migration, + gboolean success = TRUE; + str = g_strdup_printf("BEGIN;%s;PRAGMA user_version=%d;COMMIT;", migration, + sqlite3_exec(db, str, NULL, NULL, &errmsg); + g_set_error(error, PURPLE_SQLITE3_DOMAIN, 0, + "failed to run migration: %s", errmsg); + sqlite3_exec(db, "ROLLBACK", NULL, NULL, &errmsg); + g_error("failed to rollback transaction: %s", errmsg); +/****************************************************************************** + *****************************************************************************/ +purple_sqlite3_get_schema_version(sqlite3 *db, GError **error) { + sqlite3_stmt *stmt = NULL; + g_return_val_if_fail(db != NULL, -1); + sqlite3_prepare_v2(db, "PRAGMA user_version", -1, &stmt, NULL); + g_set_error(error, PURPLE_SQLITE3_DOMAIN, 0, + "error while creating prepared statement: %s", + if(sqlite3_step(stmt) == SQLITE_ROW) { + version = sqlite3_column_int(stmt, 0); + g_set_error_literal(error, PURPLE_SQLITE3_DOMAIN, 0, + "'PRAGMA user_version' didn't return a row"); + sqlite3_finalize(stmt); + sqlite3_finalize(stmt); +purple_sqlite3_run_migrations_from_strings(sqlite3 *db, + const char *migrations[], + int current_version = 0; + guint n_migrations = 0; + g_return_val_if_fail(db != NULL, FALSE); + g_return_val_if_fail(migrations != NULL, FALSE); + /* Get the current version or bail if it failed. */ + current_version = purple_sqlite3_get_schema_version(db, error); + if(current_version == -1) { + n_migrations = g_strv_length((char **)migrations); + if((guint)current_version > n_migrations) { + g_set_error(error, PURPLE_SQLITE3_DOMAIN, 0, + "schema version %u is higher than known migrations %u", + (guint)current_version, n_migrations); + for(int i = current_version; migrations[i] != NULL; i++) { + if(!purple_sqlite3_run_migration(db, version, migrations[i], error)) { +purple_sqlite3_run_migrations_from_resources(sqlite3 *db, const char *path, + const char *migrations[], + GError *local_error = NULL; + int current_version = 0; + guint n_migrations = 0; + g_return_val_if_fail(db != NULL, FALSE); + g_return_val_if_fail(path != NULL, FALSE); + g_return_val_if_fail(migrations != NULL, FALSE); + /* Get the current version or bail if it failed. */ + current_version = purple_sqlite3_get_schema_version(db, error); + if(current_version == -1) { + n_migrations = g_strv_length((char **)migrations); + if((guint)current_version > n_migrations) { + g_set_error(error, PURPLE_SQLITE3_DOMAIN, 0, + "schema version %u is higher than known migrations %u", + (guint)current_version, n_migrations); + /* `PRAGMA user_version` starts at 0, so write our version as i + 1. We + * start iterating the list of migrations at the current version of the + * database. If the database is already up to date, then current_version + * will point us at the null terminator in the list of migrations, which + * will short circuit the for loop. + for(int i = current_version; migrations[i] != NULL; i++) { + char *full_path = NULL; + const gchar *migration = NULL; + /* Get the data from the resource */ + full_path = g_build_path("/", path, migrations[i], NULL); + data = g_resources_lookup_data(full_path, G_RESOURCE_LOOKUP_FLAGS_NONE, + if(data == NULL || local_error != NULL) { + if(local_error == NULL) { + local_error = g_error_new(PURPLE_SQLITE3_DOMAIN, 0, + "failed to load resource %s", + g_propagate_error(error, local_error); + g_clear_pointer(&data, g_bytes_unref); + migration = (const char *)g_bytes_get_data(data, NULL); + if(!purple_sqlite3_run_migration(db, version, migration, error)) { --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/purplesqlite3.h Mon Oct 10 00:38:48 2022 -0500
@@ -0,0 +1,125 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 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 <https://www.gnu.org/licenses/>. +#if !defined(PURPLE_GLOBAL_HEADER_INSIDE) && !defined(PURPLE_COMPILATION) +# error "only <pidgin.h> may be included directly" +#ifndef PURPLE_SQLITE3_H +#define PURPLE_SQLITE3_H + * PURPLE_SQLITE3_ERROR: + * An error domain for sqlite3 errors. +#define PURPLE_SQLITE3_DOMAIN (g_quark_from_static_string("sqlite3")) + * purple_sqlite3_get_schema_version: + * @db: The sqlite3 connection. + * @error: (nullable): A return address for a [type@GLib.Error]. + * Attempts to read the result of `PRAGMA user_version` which this API uses to + * store the schema version. + * Returns: %TRUE on success, or %FALSE on error with @error set. +int purple_sqlite3_get_schema_version(sqlite3 *db, GError **error); + * purple_sqlite3_run_migrations_from_strings: + * @db: The sqlite3 connection. + * @migrations: (array zero-terminated=1): A list of SQL statements, each item + * being its own migration. + * @error: (nullable): A return address for a [type@GLib.Error]. + * Runs the given migrations in the order they are given. The index of each + * migration plus 1 is assumed is to be the version number of the migration, + * which means that you can not change the order of the migrations. The + * reasoning for the addition of 1 is because `PRAGMA user_version` defaults to + * This expects each string in @migrations to be a complete migration. That is, + * each string in the array should contain all of the SQL for that migration. + * For example, if you're expecting to have 2 migrations, the initial creating + * two tables, and then adding a column to one of the existing tables, you + * would have something like the following code. + * const char *migrations[] = { + * // Our initial migration that creates user and session tables. + * "CREATE TABLE user(id INTEGER PRIMARY KEY, name TEXT);" + * "CREATE TABLE session(user INTEGER, token TEXT) FOREIGN KEY(user) REFERENCES user(id);", + * // Begin our second migration that will add a display name to the user + * // table. Note the ',' at the end of the previous line. + * "ALTER TABLE user ADD COLUMN(display_name TEXT);", + * Also, this function will run each migration in its own transaction so you + * don't need to worry about them. This is done to make sure that the database + * stays at a known version and an incomplete migration will not be saved. + * Returns: %TRUE on success, or %FALSE on error potentially with @error set. +gboolean purple_sqlite3_run_migrations_from_strings(sqlite3 *db, const char *migrations[], GError **error); + * purple_sqlite3_run_migrations_from_resources: + * @db: The sqlite3 connection. + * @path: The base path in @resource to use. + * @migrations: (array zero-terminated=1): The list of migrations in the order + * @error: (nullable): A return address for a [type@GLib.Error]. + * Runs the given migrations in the order they are given. The index of each + * migration plus 1 is assumed to be the version number of the migration, which + * means that you can not change the order of the migrations. The reasoning for + * the addition of 1 is because `PRAGMA user_version` defaults to 0. + * This will attempt to load the migrations via + * [func@Gio.resources_open_stream] by concatenating @path and the individual + * items of @migrations. Each migration will be ran in a transaction that + * includes updating the schema version, which is stored in + * `PRAGMA user_version`. This means you can't use `PRAGMA user_version` for + * Returns: %TRUE on success, or %FALSE on error potentially with @error set. +gboolean purple_sqlite3_run_migrations_from_resources(sqlite3 *db, const char *path, const char *migrations[], GError **error); +#endif /* PURPLE_SQLITE3_H */ --- a/libpurple/purplesqlitehistoryadapter.c Mon Oct 10 00:27:32 2022 -0500
+++ b/libpurple/purplesqlitehistoryadapter.c Mon Oct 10 00:38:48 2022 -0500
@@ -18,13 +18,13 @@
#include <glib/gi18n-lib.h>
#include "purplesqlitehistoryadapter.h"
#include "purpleprivate.h"
-#include "purpleresources.h"
+#include "purplesqlite3.h" struct _PurpleSqliteHistoryAdapter {
PurpleHistoryAdapter parent;
@@ -60,34 +60,14 @@
purple_sqlite_history_adapter_run_migrations(PurpleSqliteHistoryAdapter *adapter,
- GResource *resource = NULL;
- gchar *error_msg = NULL;
- const gchar *script = NULL;
- resource = purple_get_resource();
- bytes = g_resource_lookup_data(resource,
- "/im/pidgin/libpurple/sqlitehistoryadapter/01-schema.sql",
- G_RESOURCE_LOOKUP_FLAGS_NONE, error);
+ const char *path = "/im/pidgin/libpurple/sqlitehistoryadapter"; + const char *migrations[] = { - script = (const gchar *)g_bytes_get_data(bytes, NULL);
- sqlite3_exec(adapter->db, script, NULL, NULL, &error_msg);
- if(error_msg != NULL) {
- g_set_error(error, PURPLE_HISTORY_ADAPTER_DOMAIN, 0,
- "failed to run migrations: %s", error_msg);
- sqlite3_free(error_msg);
+ return purple_sqlite3_run_migrations_from_resources(adapter->db, path, --- a/libpurple/resources/sqlitehistoryadapter/01-schema.sql Mon Oct 10 00:27:32 2022 -0500
+++ b/libpurple/resources/sqlitehistoryadapter/01-schema.sql Mon Oct 10 00:38:48 2022 -0500
@@ -1,4 +1,4 @@
-CREATE TABLE IF NOT EXISTS message_log
+CREATE TABLE message_log protocol TEXT NOT NULL, -- examples: slack, xmpp, irc, discord
account TEXT NOT NULL, -- example: grim@reaperworld.com@milwaukee.slack.com
--- a/libpurple/tests/meson.build Mon Oct 10 00:27:32 2022 -0500
+++ b/libpurple/tests/meson.build Mon Oct 10 00:38:48 2022 -0500
@@ -59,4 +59,6 @@
dependencies : [libpurple_dep, glib],
-test('credential_manager', e, env: testenv, is_parallel : false)
\ No newline at end of file
+test('credential_manager', e, env: testenv, is_parallel : false) --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/sqlite3/initial.sql Mon Oct 10 00:38:48 2022 -0500
@@ -0,0 +1,2 @@
+CREATE TABLE foo(a TEXT); +CREATE TABLE bar(b TEXT); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/sqlite3/malformed.sql Mon Oct 10 00:38:48 2022 -0500
@@ -0,0 +1,1 @@
+CREATE TABLE foo(a TEXT; \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/sqlite3/meson.build Mon Oct 10 00:38:48 2022 -0500
@@ -0,0 +1,16 @@
+TEST_SQLITE3_SOURCES = [ +TEST_SQLITE3_RESOURCES = gnome.compile_resources('test_sqlite3_resources', + 'test_sqlite3.gresource.xml', + c_name : 'test_sqlite3') +TEST_SQLITE3_SOURCES += TEST_SQLITE3_RESOURCES +test_sqlite3 = executable( + dependencies : [libpurple_dep, glib, sqlite3]) +test('sqlite3', test_sqlite3) --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/sqlite3/secondary.sql Mon Oct 10 00:38:48 2022 -0500
@@ -0,0 +1,1 @@
+CREATE TABLE baz(c TEXT); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/sqlite3/test_sqlite3.c Mon Oct 10 00:38:48 2022 -0500
@@ -0,0 +1,476 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * This library 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 2 of the License, 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 Lesser General Public + * License along with this library; if not, see <https://www.gnu.org/licenses/>. +/****************************************************************************** + * get schema version tests + *****************************************************************************/ +test_sqlite3_get_schema_version_null(void) { + if(g_test_subprocess()) { + version = purple_sqlite3_get_schema_version(NULL, &error); + g_assert_error(error, PURPLE_SQLITE3_DOMAIN, 0); + g_assert_cmpint(version, ==, -1); + g_test_trap_subprocess(NULL, 0, 0); + g_test_trap_assert_failed(); + g_test_trap_assert_stderr("*assertion*!= NULL*"); +test_sqlite3_get_schema_version_new(void) { + rc = sqlite3_open(":memory:", &db); + g_assert_cmpint(rc, ==, SQLITE_OK); + version = purple_sqlite3_get_schema_version(db, &error); + g_assert_no_error(error); + g_assert_cmpint(version, ==, 0); + rc = sqlite3_close(db); + g_assert_cmpint(rc, ==, SQLITE_OK); +/****************************************************************************** + * string migration tests + *****************************************************************************/ +test_sqlite3_string_migrations_null(void) { + if(g_test_subprocess()) { + rc = sqlite3_open(":memory:", &db); + g_assert_cmpint(rc, ==, SQLITE_OK); + res = purple_sqlite3_run_migrations_from_strings(db, NULL, &error); + g_assert_no_error(error); + g_test_trap_subprocess(NULL, 0, 0); + g_test_trap_assert_failed(); + g_test_trap_assert_stderr("*migrations != NULL*"); +test_sqlite3_string_migrations_null_terminator(void) { + const char *migrations[] = {NULL}; + rc = sqlite3_open(":memory:", &db); + g_assert_cmpint(rc, ==, SQLITE_OK); + res = purple_sqlite3_run_migrations_from_strings(db, migrations, &error); + g_assert_no_error(error); + version = purple_sqlite3_get_schema_version(db, &error); + g_assert_no_error(error); + g_assert_cmpint(version, ==, 0); + rc = sqlite3_close(db); + g_assert_cmpint(rc, ==, SQLITE_OK); +test_sqlite3_string_migrations_multiple(void) { + const char *migrations[] = { + "CREATE TABLE foo(a TEXT); CREATE TABLE bar(b TEXT);", + "CREATE TABLE baz(c TEXT);", + rc = sqlite3_open(":memory:", &db); + g_assert_cmpint(rc, ==, SQLITE_OK); + res = purple_sqlite3_run_migrations_from_strings(db, migrations, &error); + g_assert_no_error(error); + version = purple_sqlite3_get_schema_version(db, &error); + g_assert_no_error(error); + g_assert_cmpint(version, ==, 2); + /* Run the migrations again and make sure we remain at schema version 2. */ + res = purple_sqlite3_run_migrations_from_strings(db, migrations, &error); + g_assert_no_error(error); + version = purple_sqlite3_get_schema_version(db, &error); + g_assert_no_error(error); + g_assert_cmpint(version, ==, 2); + rc = sqlite3_close(db); + g_assert_cmpint(rc, ==, SQLITE_OK); +test_sqlite3_string_migrations_syntax_error(void) { + const char *migrations[] = { + "CREATE TABLE broke(a TEXT", + rc = sqlite3_open(":memory:", &db); + g_assert_cmpint(rc, ==, SQLITE_OK); + res = purple_sqlite3_run_migrations_from_strings(db, migrations, &error); + g_assert_error(error, PURPLE_SQLITE3_DOMAIN, 0); + version = purple_sqlite3_get_schema_version(db, &error); + g_assert_no_error(error); + g_assert_cmpint(version, ==, 0); + rc = sqlite3_close(db); + g_assert_cmpint(rc, ==, SQLITE_OK); +test_sqlite3_string_migrations_older(void) { + const char *migrations1[] = { + "CREATE TABLE foo(a TEXT); CREATE TABLE bar(b TEXT);", + "CREATE TABLE baz(c TEXT);", + const char *migrations2[] = { + "CREATE TABLE foo(a TEXT); CREATE TABLE bar(b TEXT);", + rc = sqlite3_open(":memory:", &db); + g_assert_cmpint(rc, ==, SQLITE_OK); + res = purple_sqlite3_run_migrations_from_strings(db, migrations1, &error); + g_assert_no_error(error); + version = purple_sqlite3_get_schema_version(db, &error); + g_assert_no_error(error); + g_assert_cmpint(version, ==, 2); + /* Run the older migrations now and verify we get a failure. */ + res = purple_sqlite3_run_migrations_from_strings(db, migrations2, &error); + g_assert_error(error, PURPLE_SQLITE3_DOMAIN, 0); + version = purple_sqlite3_get_schema_version(db, &error); + g_assert_no_error(error); + g_assert_cmpint(version, ==, 2); + rc = sqlite3_close(db); + g_assert_cmpint(rc, ==, SQLITE_OK); +/****************************************************************************** + * resource migration tests + *****************************************************************************/ +test_sqlite3_resource_migrations_null_path(void) { + if(g_test_subprocess()) { + rc = sqlite3_open(":memory:", &db); + g_assert_cmpint(rc, ==, SQLITE_OK); + res = purple_sqlite3_run_migrations_from_resources(db, NULL, NULL, + g_assert_no_error(error); + rc = sqlite3_close(db); + g_assert_cmpint(rc, ==, SQLITE_OK); + g_test_trap_subprocess(NULL, 0, 0); + g_test_trap_assert_failed(); + g_test_trap_assert_stderr("*path != NULL*"); +test_sqlite3_resource_migrations_null_migrations(void) { + if(g_test_subprocess()) { + const char *path = "/im/libpidgin/purple/tests/sqlite3/"; + rc = sqlite3_open(":memory:", &db); + g_assert_cmpint(rc, ==, SQLITE_OK); + res = purple_sqlite3_run_migrations_from_resources(db, path, NULL, + g_assert_no_error(error); + rc = sqlite3_close(db); + g_assert_cmpint(rc, ==, SQLITE_OK); + g_test_trap_subprocess(NULL, 0, 0); + g_test_trap_assert_failed(); + g_test_trap_assert_stderr("*migrations != NULL*"); +test_sqlite3_resource_migrations_null_terminator(void) { + const char *migrations[] = {NULL}; + const char *path = "/im/pidgin/libpurple/tests/sqlite3/"; + rc = sqlite3_open(":memory:", &db); + g_assert_cmpint(rc, ==, SQLITE_OK); + res = purple_sqlite3_run_migrations_from_resources(db, path, migrations, + g_assert_no_error(error); + version = purple_sqlite3_get_schema_version(db, &error); + g_assert_no_error(error); + g_assert_cmpint(version, ==, 0); + rc = sqlite3_close(db); + g_assert_cmpint(rc, ==, SQLITE_OK); +test_sqlite3_resource_migrations_multiple(void) { + const char *migrations[] = {"initial.sql", "secondary.sql", NULL}; + const char *path = "/im/pidgin/libpurple/tests/sqlite3/"; + rc = sqlite3_open(":memory:", &db); + g_assert_cmpint(rc, ==, SQLITE_OK); + res = purple_sqlite3_run_migrations_from_resources(db, path, migrations, + g_assert_no_error(error); + version = purple_sqlite3_get_schema_version(db, &error); + g_assert_no_error(error); + g_assert_cmpint(version, ==, 2); + /* Run the migrations again and make sure we remain at schema version 2. */ + res = purple_sqlite3_run_migrations_from_strings(db, migrations, &error); + g_assert_no_error(error); + version = purple_sqlite3_get_schema_version(db, &error); + g_assert_no_error(error); + g_assert_cmpint(version, ==, 2); + rc = sqlite3_close(db); + g_assert_cmpint(rc, ==, SQLITE_OK); +test_sqlite3_resource_migrations_missing(void) { + const char *migrations[] = {"initial.sql", "imaginary.sql", NULL}; + const char *path = "/im/pidgin/libpurple/tests/sqlite3/"; + rc = sqlite3_open(":memory:", &db); + g_assert_cmpint(rc, ==, SQLITE_OK); + res = purple_sqlite3_run_migrations_from_resources(db, path, migrations, + g_assert_error(error, G_RESOURCE_ERROR, 0); + version = purple_sqlite3_get_schema_version(db, &error); + g_assert_no_error(error); + g_assert_cmpint(version, ==, 1); + rc = sqlite3_close(db); + g_assert_cmpint(rc, ==, SQLITE_OK); +test_sqlite3_resource_migrations_syntax_error(void) { + const char *migrations[] = {"malformed.sql", NULL}; + const char *path = "/im/pidgin/libpurple/tests/sqlite3/"; + rc = sqlite3_open(":memory:", &db); + g_assert_cmpint(rc, ==, SQLITE_OK); + res = purple_sqlite3_run_migrations_from_resources(db, path, migrations, + g_assert_error(error, PURPLE_SQLITE3_DOMAIN, 0); + version = purple_sqlite3_get_schema_version(db, &error); + g_assert_no_error(error); + g_assert_cmpint(version, ==, 0); + rc = sqlite3_close(db); + g_assert_cmpint(rc, ==, SQLITE_OK); +test_sqlite3_resource_migrations_older(void) { + const char *migrations1[] = {"initial.sql", "secondary.sql", NULL}; + const char *migrations2[] = {"initial.sql", NULL}; + const char *path = "/im/pidgin/libpurple/tests/sqlite3/"; + rc = sqlite3_open(":memory:", &db); + g_assert_cmpint(rc, ==, SQLITE_OK); + res = purple_sqlite3_run_migrations_from_resources(db, path, migrations1, + g_assert_no_error(error); + version = purple_sqlite3_get_schema_version(db, &error); + g_assert_no_error(error); + g_assert_cmpint(version, ==, 2); + /* Run the older migrations now and verify we get a failure. */ + res = purple_sqlite3_run_migrations_from_resources(db, path, migrations2, + g_assert_error(error, PURPLE_SQLITE3_DOMAIN, 0); + version = purple_sqlite3_get_schema_version(db, &error); + g_assert_no_error(error); + g_assert_cmpint(version, ==, 2); + rc = sqlite3_close(db); + g_assert_cmpint(rc, ==, SQLITE_OK); +/****************************************************************************** + *****************************************************************************/ +main(int argc, char *argv[]) { + g_test_init(&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL); + g_test_add_func("/sqlite3/schema_version/null", + test_sqlite3_get_schema_version_null); + g_test_add_func("/sqlite3/schema_version/new", + test_sqlite3_get_schema_version_new); + g_test_add_func("/sqlite3/string_migrations/null", + test_sqlite3_string_migrations_null); + g_test_add_func("/sqlite3/string_migrations/null-terminator", + test_sqlite3_string_migrations_null_terminator); + g_test_add_func("/sqlite3/string_migrations/multiple", + test_sqlite3_string_migrations_multiple); + g_test_add_func("/sqlite3/string_migrations/syntax-error", + test_sqlite3_string_migrations_syntax_error); + g_test_add_func("/sqlite3/string_migrations/older", + test_sqlite3_string_migrations_older); + g_test_add_func("/sqlite3/resource_migrations/null-path", + test_sqlite3_resource_migrations_null_path); + g_test_add_func("/sqlite3/resource_migrations/null-migrations", + test_sqlite3_resource_migrations_null_migrations); + g_test_add_func("/sqlite3/resource_migrations/null-terminator", + test_sqlite3_resource_migrations_null_terminator); + g_test_add_func("/sqlite3/resource_migrations/multiple", + test_sqlite3_resource_migrations_multiple); + g_test_add_func("/sqlite3/resource_migrations/missing", + test_sqlite3_resource_migrations_missing); + g_test_add_func("/sqlite3/resource_migrations/syntax-error", + test_sqlite3_resource_migrations_syntax_error); + g_test_add_func("/sqlite3/resource_migrations/older", + test_sqlite3_resource_migrations_older); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/sqlite3/test_sqlite3.gresource.xml Mon Oct 10 00:38:48 2022 -0500
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?> + <gresource prefix="/im/pidgin/libpurple/tests/sqlite3/"> + <file compressed="true">initial.sql</file> + <file compressed="true">secondary.sql</file> + <file compressed="true">malformed.sql</file>