Skip to content

Commit

Permalink
compose: Add quickcheck test for traversal
Browse files Browse the repository at this point in the history
Test against the `foreach` implementation:
- Suffle compose file lines randomly;
- Compare traversal entry by entry.
  • Loading branch information
wismill committed Aug 28, 2024
1 parent e9bd7de commit f5cacd8
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 4 deletions.
11 changes: 10 additions & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -765,7 +765,15 @@ test(
)
test(
'compose',
executable('test-compose', 'test/compose.c', dependencies: test_dep),
executable(
'test-compose',
'test/compose.c',
'test/shuffle-lines.c',
'test/shuffle-lines.h',
'test/compose-iter.c',
'test/compose-iter.h',
dependencies: test_dep
),
env: test_env,
)
test(
Expand Down Expand Up @@ -853,6 +861,7 @@ if valgrind.found()
'--track-origins=yes',
'--gen-suppressions=all',
'--error-exitcode=99'],
env: {'RUNNING_VALGRIND': '1'},
timeout_multiplier : 10)
else
message('valgrind not found, disabling valgrind test setup')
Expand Down
88 changes: 88 additions & 0 deletions test/compose-iter.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#include "config.h"

#include "src/darray.h"
#include <stdio.h>
#include <stdbool.h>
#include <string.h>

#include "xkbcommon/xkbcommon-compose.h"
#include "src/compose/dump.h"
#include "src/compose/parser.h"
#include "src/keysym.h"
#include "src/utils.h"
#include "test/compose-iter.h"

static void
for_each_helper(struct xkb_compose_table *table,
xkb_compose_table_iter_t iter,
void *data,
xkb_keysym_t *syms,
size_t nsyms,
uint16_t p)
{
if (!p) {
return;
}
const struct compose_node *node = &darray_item(table->nodes, p);
for_each_helper(table, iter, data, syms, nsyms, node->lokid);
syms[nsyms++] = node->keysym;
if (node->is_leaf) {
struct xkb_compose_table_entry entry = {
.sequence = syms,
.sequence_length = nsyms,
.keysym = node->leaf.keysym,
.utf8 = &darray_item(table->utf8, node->leaf.utf8),
};
iter(&entry, data);
} else {
for_each_helper(table, iter, data, syms, nsyms, node->internal.eqkid);
}
nsyms--;
for_each_helper(table, iter, data, syms, nsyms, node->hikid);
}

XKB_EXPORT void
xkb_compose_table_for_each(struct xkb_compose_table *table,
xkb_compose_table_iter_t iter,
void *data)
{
if (darray_size(table->nodes) <= 1) {
return;
}
xkb_keysym_t syms[MAX_LHS_LEN];
for_each_helper(table, iter, data, syms, 0, 1);
}

bool
print_compose_table_entry(FILE *file, struct xkb_compose_table_entry *entry)
{
size_t nsyms;
const xkb_keysym_t *syms = xkb_compose_table_entry_sequence(entry, &nsyms);
char buf[XKB_KEYSYM_NAME_MAX_SIZE];
for (size_t i = 0; i < nsyms; i++) {
xkb_keysym_get_name(syms[i], buf, sizeof(buf));
fprintf(file, "<%s>", buf);
if (i + 1 < nsyms) {
fprintf(file, " ");
}
}
fprintf(file, " : ");
const char *utf8 = xkb_compose_table_entry_utf8(entry);
if (*utf8 != '\0') {
char *escaped = escape_utf8_string_literal(utf8);
if (!escaped) {
fprintf(file, "ERROR: Cannot escape the string: allocation error\n");
return false;
} else {
fprintf(file, " \"%s\"", escaped);
free(escaped);
}
}
const xkb_keysym_t keysym = xkb_compose_table_entry_keysym(entry);
if (keysym != XKB_KEY_NoSymbol) {
xkb_keysym_get_name(keysym, buf, sizeof(buf));
fprintf(file, " %s", buf);
}
fprintf(file, "\n");
return true;
}
36 changes: 36 additions & 0 deletions test/compose-iter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#ifndef COMPOSE_LEGACY_ITER_H
#define COMPOSE_LEGACY_ITER_H

#include "config.h"
#include "src/compose/table.h"

/**
* The iterator function type used by xkb_compose_table_for_each().
*
* @sa xkb_compose_table_for_each
* @memberof xkb_compose
* @since 1.6.0
*/
typedef void
(*xkb_compose_table_iter_t)(struct xkb_compose_table_entry *entry,
void *data);

/**
* Run a specified function for every valid entry in the table.
*
* The entries are returned in lexicographic order of the left-hand
* side of entries. This does not correspond to the order in which
* the entries appear in the Compose file.
*
* @memberof xkb_compose_table
* @since 1.6.0
*/
void
xkb_compose_table_for_each(struct xkb_compose_table *table,
xkb_compose_table_iter_t iter,
void *data);

bool
print_compose_table_entry(FILE *file, struct xkb_compose_table_entry *entry);

#endif
83 changes: 80 additions & 3 deletions test/compose.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
#include "src/keysym.h"
#include "src/compose/parser.h"
#include "src/compose/dump.h"
#include "test/shuffle-lines.h"
#include "test/compose-iter.h"

static const char *
compose_status_string(enum xkb_compose_status status)
Expand Down Expand Up @@ -717,8 +719,45 @@ test_eq_entry(struct xkb_compose_table_entry *entry, xkb_keysym_t keysym, const
return ok;
}

static bool
test_eq_entries(struct xkb_compose_table_entry *entry1, struct xkb_compose_table_entry *entry2)
{
if (!entry1 || !entry2)
goto error;
bool ok = true;
if (entry1->keysym != entry2->keysym ||
!streq_null(entry1->utf8, entry2->utf8) ||
entry1->sequence_length != entry2->sequence_length)
ok = false;
for (size_t k = 0; k < entry1->sequence_length; k++) {
if (entry1->sequence[k] != entry2->sequence[k])
ok = false;
}
if (ok)
return true;
error:
#define print_entry(msg, entry) \
fprintf(stderr, msg); \
if (entry) \
print_compose_table_entry(stderr, entry); \
else \
fprintf(stderr, "\n");
print_entry("Expected: ", entry1);
print_entry("Got: ", entry2);
#undef print_entry
return false;
}

static void
compose_traverse_fn(struct xkb_compose_table_entry *entry_ref, void *data)
{
struct xkb_compose_table_iterator *iter = (struct xkb_compose_table_iterator *)data;
struct xkb_compose_table_entry *entry = xkb_compose_table_iterator_next(iter);
assert(test_eq_entries(entry_ref, entry));
}

static void
test_traverse(struct xkb_context *ctx)
test_traverse(struct xkb_context *ctx, size_t quickcheck_loops)
{
struct xkb_compose_table *table;
struct xkb_compose_table_iterator *iter;
Expand Down Expand Up @@ -796,6 +835,34 @@ test_traverse(struct xkb_context *ctx)

xkb_compose_table_iterator_free(iter);
xkb_compose_table_unref(table);

/* QuickCheck: shuffle compose file lines and compare against
* reference implementation */
char *input = test_read_file("locale/en_US.UTF-8/Compose");
assert(input);
struct text_line lines[6000];
size_t input_length = strlen(input);
size_t lines_count = split_lines(input, input_length, lines, ARRAY_SIZE(lines));
/* Note: we may add additional new line char */
char *shuffled = calloc(input_length + 1, sizeof(char));
assert(shuffled);
for (size_t k = 0; k < quickcheck_loops; k++) {
size_t shuffled_length = shuffle_lines(lines, lines_count, shuffled);
table = xkb_compose_table_new_from_buffer(ctx, shuffled, shuffled_length, "",
XKB_COMPOSE_FORMAT_TEXT_V1,
XKB_COMPOSE_COMPILE_NO_FLAGS);
assert(table);

iter = xkb_compose_table_iterator_new(table);
assert(iter);
xkb_compose_table_for_each(table, compose_traverse_fn, iter);
assert(xkb_compose_table_iterator_next(iter) == NULL);
xkb_compose_table_iterator_free(iter);

xkb_compose_table_unref(table);
}
free(shuffled);
free(input);
}

static void
Expand Down Expand Up @@ -950,14 +1017,24 @@ main(int argc, char *argv[])

/* Initialize pseudo-random generator with program arg or current time */
int seed;
if (argc == 2) {
if (argc >= 2 && !streq(argv[1], "-")) {
seed = atoi(argv[1]);
} else {
seed = (int)time(NULL);
}
fprintf(stderr, "Seed for the pseudo-random generator: %d\n", seed);
srand(seed);

/* Determine number of loops for quickchecks */
size_t quickcheck_loops = 100; /* Default */
if (argc > 2) {
/* From command-line */
quickcheck_loops = (size_t)atoi(argv[2]);
} else if (getenv("RUNNING_VALGRIND") != NULL) {
/* Reduce if running Valgrind */
quickcheck_loops = quickcheck_loops / 20;
}

/*
* Ensure no environment variables but “top_srcdir” is set. This ensures
* that user Compose file paths are unset before the tests and set
Expand Down Expand Up @@ -985,7 +1062,7 @@ main(int argc, char *argv[])
test_modifier_syntax(ctx);
test_include(ctx);
test_override(ctx);
test_traverse(ctx);
test_traverse(ctx, quickcheck_loops);
test_string_length(ctx);
test_decode_escape_sequences(ctx);
test_encode_escape_sequences(ctx);
Expand Down
59 changes: 59 additions & 0 deletions test/shuffle-lines.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#include "config.h"

#include <stdlib.h>
#include <string.h>
#include "src/utils.h"
#include "test/shuffle-lines.h"

/* Split string into lines */
size_t
split_lines(const char *input, size_t input_length,
struct text_line *output, size_t output_length)
{
const char *start = input;
char *next;
size_t l;
size_t i = 0;

for (l = 0; i < input_length && l < output_length && *start != '\0'; l++) {
/* Look for newline character */
next = strchr(start, 0x0a);
output[l].start = start;
if (next == NULL) {
/* Not found: add the rest of the string */
output[l++].length = strlen(start);
break;
}
output[l].length = (size_t)(next - start) + 1;
start = next + 1;
i += output[l].length;
}
return l;
}

size_t
shuffle_lines(struct text_line *lines, size_t length, char *output)
{
/* Shuffle in-place using Fisher–Yates algorithm, then append lines.
* See: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle */

assert(length < RAND_MAX);
char *out = output;
if (length > 1) {
for (size_t i = length - 1; i > 0; i--) {
size_t j = (size_t)(rand() % (i+1));
struct text_line tmp = lines[j];
lines[j] = lines[i];
lines[i] = tmp;
/* Append current line */
memcpy(out, lines[i].start, lines[i].length);
out += lines[i].length;
/* Ensure line ends with newline */
if (out[-1] != '\n') {
out[0] = '\n';
out++;
}
}
}
return (size_t)(out - output);
}
13 changes: 13 additions & 0 deletions test/shuffle-lines.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include <stdint.h>

struct text_line {
const char *start;
size_t length;
};

size_t
split_lines(const char *input, size_t input_length,
struct text_line *output, size_t output_length);

size_t
shuffle_lines(struct text_line *lines, size_t length, char *output);

0 comments on commit f5cacd8

Please sign in to comment.