Add a test for the dictionary
This commit is contained in:
parent
4f4a86529a
commit
cdb0ec474c
13
Makefile
13
Makefile
|
@ -1,14 +1,15 @@
|
||||||
SHELL = /bin/sh
|
SHELL = /bin/sh
|
||||||
|
|
||||||
pkgs = ncursesw glib-2.0 gio-2.0
|
pkgs = ncursesw glib-2.0 gio-2.0
|
||||||
targets = sdcli
|
tests = test-stardict
|
||||||
|
targets = sdcli $(tests)
|
||||||
|
|
||||||
CC = clang
|
CC = clang
|
||||||
CFLAGS = -ggdb -std=gnu99 -Wall -Wextra -Wno-missing-field-initializers \
|
CFLAGS = -ggdb -std=gnu99 -Wall -Wextra -Wno-missing-field-initializers \
|
||||||
`pkg-config --cflags $(pkgs)`
|
`pkg-config --cflags $(pkgs)`
|
||||||
LDFLAGS = `pkg-config --libs $(pkgs)`
|
LDFLAGS = `pkg-config --libs $(pkgs)`
|
||||||
|
|
||||||
.PHONY: all clean
|
.PHONY: all clean test
|
||||||
|
|
||||||
all: $(targets)
|
all: $(targets)
|
||||||
|
|
||||||
|
@ -18,5 +19,13 @@ clean:
|
||||||
sdcli: sdcli.o stardict.o
|
sdcli: sdcli.o stardict.o
|
||||||
$(CC) $^ -o $@ $(LDFLAGS)
|
$(CC) $^ -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
|
test-stardict: test-stardict.o stardict.o
|
||||||
|
$(CC) $^ -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
|
test: $(tests)
|
||||||
|
for i in $(tests); do \
|
||||||
|
gtester --verbose ./$$i; \
|
||||||
|
done
|
||||||
|
|
||||||
%.o: %.c
|
%.o: %.c
|
||||||
$(CC) $(CFLAGS) -c $< -o $@
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
|
@ -0,0 +1,427 @@
|
||||||
|
/*
|
||||||
|
* stardict.c: StarDict API test
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013, Přemysl Janouch <p.janouch@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
|
#include "stardict.h"
|
||||||
|
|
||||||
|
|
||||||
|
// --- Utilities ---------------------------------------------------------------
|
||||||
|
|
||||||
|
// Adapted http://gezeiten.org/post/2009/04/Writing-Your-Own-GIO-Jobs
|
||||||
|
static gboolean remove_recursive (GFile *file, GError **error);
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
remove_directory_contents (GFile *file, GError **error)
|
||||||
|
{
|
||||||
|
GFileEnumerator *enumerator =
|
||||||
|
g_file_enumerate_children (file, "standard::*",
|
||||||
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, error);
|
||||||
|
|
||||||
|
if (!enumerator)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
gboolean success = TRUE;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
GError *err = NULL;
|
||||||
|
GFileInfo *child_info =
|
||||||
|
g_file_enumerator_next_file (enumerator, NULL, &err);
|
||||||
|
|
||||||
|
if (!child_info)
|
||||||
|
{
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
g_propagate_error (error, err);
|
||||||
|
success = FALSE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
GFile *child = g_file_resolve_relative_path
|
||||||
|
(file, g_file_info_get_name (child_info));
|
||||||
|
success = remove_recursive (child, error);
|
||||||
|
g_object_unref (child);
|
||||||
|
g_object_unref (child_info);
|
||||||
|
}
|
||||||
|
while (success);
|
||||||
|
|
||||||
|
g_object_unref (enumerator);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
remove_recursive (GFile *file, GError **error)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (G_IS_FILE (file), FALSE);
|
||||||
|
|
||||||
|
GFileInfo *info = g_file_query_info (file, "standard::*",
|
||||||
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, error);
|
||||||
|
|
||||||
|
if (!info)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
GFileType type = g_file_info_get_file_type (info);
|
||||||
|
g_object_unref (info);
|
||||||
|
|
||||||
|
if (type == G_FILE_TYPE_DIRECTORY &&
|
||||||
|
!remove_directory_contents (file, error))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
return g_file_delete (file, NULL, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gchar *
|
||||||
|
generate_random_string (gsize length, GRand *rand)
|
||||||
|
{
|
||||||
|
GString *s = g_string_sized_new (length);
|
||||||
|
while (length--)
|
||||||
|
g_string_append_c (s, g_rand_int_range (rand, 'a', 'z' + 1));
|
||||||
|
return g_string_free (s, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gpointer
|
||||||
|
generate_random_data (gsize length, GRand *rand)
|
||||||
|
{
|
||||||
|
gchar *blob = g_malloc (length), *i = blob;
|
||||||
|
while (length--)
|
||||||
|
*i++ = g_rand_int_range (rand, 0, 256);
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Dictionary generation ---------------------------------------------------
|
||||||
|
|
||||||
|
typedef struct dictionary Dictionary;
|
||||||
|
typedef struct test_entry TestEntry;
|
||||||
|
|
||||||
|
struct dictionary
|
||||||
|
{
|
||||||
|
GFile *tmp_dir; //!< A temporary dictionary
|
||||||
|
GFile *ifo_file; //!< The dictionary's .ifo file
|
||||||
|
GArray *data; //!< Array of TestEntry's
|
||||||
|
};
|
||||||
|
|
||||||
|
struct test_entry
|
||||||
|
{
|
||||||
|
gchar *word;
|
||||||
|
gchar *meaning;
|
||||||
|
gpointer data;
|
||||||
|
gsize data_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_entry_free (TestEntry *te)
|
||||||
|
{
|
||||||
|
g_free (te->word);
|
||||||
|
g_free (te->meaning);
|
||||||
|
g_free (te->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gint
|
||||||
|
test_entry_word_compare (gconstpointer a, gconstpointer b)
|
||||||
|
{
|
||||||
|
return strcmp (((TestEntry *) a)->word, ((TestEntry *) b)->word);
|
||||||
|
}
|
||||||
|
|
||||||
|
static GArray *
|
||||||
|
generate_dictionary_data (gsize length)
|
||||||
|
{
|
||||||
|
GRand *rand = g_rand_new_with_seed (0);
|
||||||
|
|
||||||
|
GArray *a = g_array_sized_new (FALSE, FALSE, sizeof (TestEntry), length);
|
||||||
|
g_array_set_clear_func (a, (GDestroyNotify) test_entry_free);
|
||||||
|
|
||||||
|
while (length--)
|
||||||
|
{
|
||||||
|
TestEntry te;
|
||||||
|
|
||||||
|
te.word = generate_random_string
|
||||||
|
(g_rand_int_range (rand, 1, 10), rand);
|
||||||
|
te.meaning = generate_random_string
|
||||||
|
(g_rand_int_range (rand, 1, 1024), rand);
|
||||||
|
|
||||||
|
te.data_size = g_rand_int_range (rand, 0, 1048576);
|
||||||
|
te.data = generate_random_data (te.data_size, rand);
|
||||||
|
|
||||||
|
g_array_append_val (a, te);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_rand_free (rand);
|
||||||
|
g_array_sort (a, test_entry_word_compare);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Dictionary *
|
||||||
|
dictionary_create (void)
|
||||||
|
{
|
||||||
|
GError *error;
|
||||||
|
gchar *tmp_dir_path = g_dir_make_tmp ("stardict-test-XXXXXX", &error);
|
||||||
|
if (!tmp_dir_path)
|
||||||
|
g_error ("Failed to create a directory for the test dictionary: %s",
|
||||||
|
error->message);
|
||||||
|
|
||||||
|
Dictionary *dict = g_malloc (sizeof *dict);
|
||||||
|
dict->tmp_dir = g_file_new_for_path (tmp_dir_path);
|
||||||
|
|
||||||
|
static const gint dictionary_size = 8;
|
||||||
|
dict->data = generate_dictionary_data (dictionary_size);
|
||||||
|
GFile *dict_file = g_file_get_child (dict->tmp_dir, "test.dict");
|
||||||
|
GFile *idx_file = g_file_get_child (dict->tmp_dir, "test.idx");
|
||||||
|
|
||||||
|
GFileOutputStream *dict_stream = g_file_replace (dict_file,
|
||||||
|
NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error);
|
||||||
|
if (!dict_stream)
|
||||||
|
g_error ("Failed to create the .dict file: %s", error->message);
|
||||||
|
|
||||||
|
GFileOutputStream *idx_stream = g_file_replace (idx_file,
|
||||||
|
NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error);
|
||||||
|
if (!idx_stream)
|
||||||
|
g_error ("Failed to create the .idx file: %s", error->message);
|
||||||
|
|
||||||
|
GDataOutputStream *dict_data
|
||||||
|
= g_data_output_stream_new (G_OUTPUT_STREAM (dict_stream));
|
||||||
|
g_data_output_stream_set_byte_order
|
||||||
|
(dict_data, G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN);
|
||||||
|
|
||||||
|
GDataOutputStream *idx_data
|
||||||
|
= g_data_output_stream_new (G_OUTPUT_STREAM (idx_stream));
|
||||||
|
g_data_output_stream_set_byte_order
|
||||||
|
(idx_data, G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN);
|
||||||
|
|
||||||
|
gint i;
|
||||||
|
gsize written;
|
||||||
|
for (i = 0; i < dictionary_size; i++)
|
||||||
|
{
|
||||||
|
TestEntry *te = &g_array_index (dict->data, TestEntry, i);
|
||||||
|
goffset offset = g_seekable_tell (G_SEEKABLE (dict_stream));
|
||||||
|
|
||||||
|
if (!g_data_output_stream_put_string (dict_data,
|
||||||
|
te->meaning, NULL, &error)
|
||||||
|
|| !g_data_output_stream_put_byte (dict_data, '\0', NULL, &error)
|
||||||
|
|| !g_output_stream_write_all (G_OUTPUT_STREAM (dict_stream),
|
||||||
|
te->data, te->data_size, &written, NULL, &error))
|
||||||
|
g_error ("Write to dictionary failed: %s", error->message);
|
||||||
|
|
||||||
|
if (!g_data_output_stream_put_string (idx_data,
|
||||||
|
te->word, NULL, &error)
|
||||||
|
|| !g_data_output_stream_put_byte (idx_data, '\0', NULL, &error)
|
||||||
|
|| !g_data_output_stream_put_uint32 (idx_data, offset, NULL, &error)
|
||||||
|
|| !g_data_output_stream_put_uint32 (idx_data,
|
||||||
|
g_seekable_tell (G_SEEKABLE (dict_stream)) - offset, NULL, &error))
|
||||||
|
g_error ("Write to index failed: %s", error->message);
|
||||||
|
}
|
||||||
|
|
||||||
|
gint index_size = g_seekable_tell (G_SEEKABLE (idx_stream));
|
||||||
|
|
||||||
|
if (!g_output_stream_close (G_OUTPUT_STREAM (dict_stream), NULL, &error))
|
||||||
|
g_error ("Failed to close the .dict file: %s", error->message);
|
||||||
|
if (!g_output_stream_close (G_OUTPUT_STREAM (idx_stream), NULL, &error))
|
||||||
|
g_error ("Failed to close the .idx file: %s", error->message);
|
||||||
|
|
||||||
|
g_object_unref (dict_data);
|
||||||
|
g_object_unref (idx_data);
|
||||||
|
|
||||||
|
g_object_unref (dict_stream);
|
||||||
|
g_object_unref (idx_stream);
|
||||||
|
|
||||||
|
gchar *ifo_contents = g_strdup_printf
|
||||||
|
("StarDict's dict ifo file\n"
|
||||||
|
"version=3.0.0\n"
|
||||||
|
"bookname=Test Book\n"
|
||||||
|
"wordcount=%d\n"
|
||||||
|
"idxfilesize=%d\n"
|
||||||
|
"idxoffsetbits=32\n"
|
||||||
|
"author=Lyra Heartstrings\n"
|
||||||
|
"email=lyra@equestria.net\n"
|
||||||
|
"website=http://equestria.net\n"
|
||||||
|
"description=Test dictionary\n"
|
||||||
|
"date=21.12.2012\n"
|
||||||
|
"sametypesequence=mX\n",
|
||||||
|
dictionary_size, index_size);
|
||||||
|
|
||||||
|
g_object_unref (dict_file);
|
||||||
|
g_object_unref (idx_file);
|
||||||
|
|
||||||
|
dict->ifo_file = g_file_get_child (dict->tmp_dir, "test.ifo");
|
||||||
|
if (!g_file_replace_contents (dict->ifo_file,
|
||||||
|
ifo_contents, strlen (ifo_contents),
|
||||||
|
NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, &error))
|
||||||
|
g_error ("Failed to create the .ifo file: %s", error->message);
|
||||||
|
g_free (ifo_contents);
|
||||||
|
|
||||||
|
g_message ("Successfully created a test dictionary in %s", tmp_dir_path);
|
||||||
|
g_free (tmp_dir_path);
|
||||||
|
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dictionary_destroy (Dictionary *dict)
|
||||||
|
{
|
||||||
|
GError *error;
|
||||||
|
if (!remove_recursive (dict->tmp_dir, &error))
|
||||||
|
g_error ("Failed to delete the temporary directory: %s",
|
||||||
|
error->message);
|
||||||
|
|
||||||
|
g_message ("The test dictionary has been deleted");
|
||||||
|
|
||||||
|
g_object_unref (dict->tmp_dir);
|
||||||
|
g_object_unref (dict->ifo_file);
|
||||||
|
g_array_free (dict->data, TRUE);
|
||||||
|
g_free (dict);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Testing -----------------------------------------------------------------
|
||||||
|
|
||||||
|
typedef struct dict_fixture DictFixture;
|
||||||
|
|
||||||
|
struct dict_fixture
|
||||||
|
{
|
||||||
|
StardictDict *dict;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
dict_setup (DictFixture *fixture, gconstpointer test_data)
|
||||||
|
{
|
||||||
|
Dictionary *dict = (Dictionary *) test_data;
|
||||||
|
|
||||||
|
gchar *ifo_filename = g_file_get_path (dict->ifo_file);
|
||||||
|
fixture->dict = stardict_dict_new (ifo_filename, NULL);
|
||||||
|
g_free (ifo_filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dict_teardown (DictFixture *fixture, G_GNUC_UNUSED gconstpointer test_data)
|
||||||
|
{
|
||||||
|
g_object_unref (fixture->dict);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dict_test_list (gconstpointer user_data)
|
||||||
|
{
|
||||||
|
Dictionary *dict = (Dictionary *) user_data;
|
||||||
|
|
||||||
|
gchar *tmp_path = g_file_get_path (dict->tmp_dir);
|
||||||
|
GList *dictionaries = stardict_list_dictionaries (tmp_path);
|
||||||
|
g_free (tmp_path);
|
||||||
|
|
||||||
|
g_assert (dictionaries != NULL);
|
||||||
|
g_assert (dictionaries->next == NULL);
|
||||||
|
|
||||||
|
StardictInfo *info = dictionaries->data;
|
||||||
|
GFile *ifo_file = g_file_new_for_path (stardict_info_get_path (info));
|
||||||
|
g_assert (g_file_equal (ifo_file, dict->ifo_file) == TRUE);
|
||||||
|
g_object_unref (ifo_file);
|
||||||
|
|
||||||
|
g_list_free_full (dictionaries, (GDestroyNotify) stardict_info_free);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dict_test_new (gconstpointer user_data)
|
||||||
|
{
|
||||||
|
Dictionary *dict = (Dictionary *) user_data;
|
||||||
|
|
||||||
|
gchar *ifo_filename = g_file_get_path (dict->ifo_file);
|
||||||
|
StardictDict *sd = stardict_dict_new (ifo_filename, NULL);
|
||||||
|
g_free (ifo_filename);
|
||||||
|
|
||||||
|
g_assert (sd != NULL);
|
||||||
|
g_object_unref (sd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dict_test_data_entry (StardictDict *sd, TestEntry *entry)
|
||||||
|
{
|
||||||
|
gboolean success;
|
||||||
|
StardictIterator *sdi =
|
||||||
|
stardict_dict_search (sd, entry->word, &success);
|
||||||
|
|
||||||
|
g_assert (success == TRUE);
|
||||||
|
g_assert (sdi != NULL);
|
||||||
|
g_assert (stardict_iterator_is_valid (sdi));
|
||||||
|
|
||||||
|
const gchar *word = stardict_iterator_get_word (sdi);
|
||||||
|
g_assert_cmpstr (word, ==, entry->word);
|
||||||
|
|
||||||
|
StardictEntry *sde = stardict_iterator_get_entry (sdi);
|
||||||
|
g_assert (sde != NULL);
|
||||||
|
|
||||||
|
const GList *fields = stardict_entry_get_fields (sde);
|
||||||
|
const StardictEntryField *sdef;
|
||||||
|
g_assert (fields != NULL);
|
||||||
|
g_assert (fields->data != NULL);
|
||||||
|
|
||||||
|
sdef = fields->data;
|
||||||
|
g_assert (sdef->type == 'm');
|
||||||
|
g_assert_cmpstr (sdef->data, ==, entry->meaning);
|
||||||
|
|
||||||
|
fields = fields->next;
|
||||||
|
g_assert (fields != NULL);
|
||||||
|
g_assert (fields->data != NULL);
|
||||||
|
|
||||||
|
sdef = fields->data;
|
||||||
|
g_assert (sdef->type == 'X');
|
||||||
|
g_assert_cmpuint (sdef->data_size, ==, entry->data_size);
|
||||||
|
g_assert (memcmp (sdef->data, entry->data, entry->data_size) == 0);
|
||||||
|
|
||||||
|
fields = fields->next;
|
||||||
|
g_assert (fields == NULL);
|
||||||
|
|
||||||
|
g_object_unref (sde);
|
||||||
|
g_object_unref (sdi);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dict_test_data (DictFixture *fixture, gconstpointer user_data)
|
||||||
|
{
|
||||||
|
Dictionary *dict = (Dictionary *) user_data;
|
||||||
|
GArray *data = dict->data;
|
||||||
|
StardictDict *sd = fixture->dict;
|
||||||
|
|
||||||
|
guint i;
|
||||||
|
for (i = 0; i < data->len; i++)
|
||||||
|
{
|
||||||
|
TestEntry *entry = &g_array_index (data, TestEntry, i);
|
||||||
|
dict_test_data_entry (sd, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc, char *argv[])
|
||||||
|
{
|
||||||
|
g_test_init (&argc, &argv, NULL);
|
||||||
|
|
||||||
|
Dictionary *dict = dictionary_create ();
|
||||||
|
|
||||||
|
g_test_add_data_func ("/dict/list", dict, dict_test_list);
|
||||||
|
g_test_add_data_func ("/dict/new", dict, dict_test_new);
|
||||||
|
|
||||||
|
g_test_add ("/dict/data", DictFixture, dict,
|
||||||
|
dict_setup, dict_test_data, dict_teardown);
|
||||||
|
|
||||||
|
int result = g_test_run ();
|
||||||
|
dictionary_destroy (dict);
|
||||||
|
return result;
|
||||||
|
}
|
Loading…
Reference in New Issue