summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/Makefile2
-rw-r--r--test/cli.c827
-rw-r--r--test/cli.h99
-rw-r--r--test/decode_gif.c250
-rw-r--r--test/nsgif.c442
-rwxr-xr-xtest/runtest.sh60
6 files changed, 1400 insertions, 280 deletions
diff --git a/test/Makefile b/test/Makefile
index f067d81..a578aef 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -1,3 +1,3 @@
-DIR_TEST_ITEMS := decode_gif:decode_gif.c
+DIR_TEST_ITEMS := nsgif:nsgif.c
include $(NSBUILD)/Makefile.subdir
diff --git a/test/cli.c b/test/cli.c
new file mode 100644
index 0000000..9c095fe
--- /dev/null
+++ b/test/cli.c
@@ -0,0 +1,827 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (C) 2021-2022 Michael Drake <tlsa@netsurf-browser.org>
+ */
+
+/**
+ * \file
+ * \brief Command line argument handling.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "cli.h"
+
+/**
+ * CLI parsing context.
+ */
+struct cli_ctx {
+ const struct cli_table *cli; /**< Client CLI spec. */
+ size_t pos_count; /**< The number of positional arguments found. */
+ bool no_pos; /**< Have an argument that negates min_positional. */
+};
+
+/**
+ * Check whether a CLI argument type should have a numerical value.
+ *
+ * \param[in] type An argument type.
+ * \return true if the argument needs a numerical value, or false otherwise.
+ */
+static inline bool cli__arg_is_numerical(enum cli_arg_type type)
+{
+ return (type != CLI_STRING && type != CLI_BOOL);
+}
+
+/**
+ * Parse a signed integer value from an argument.
+ *
+ * \param[in] str String containing value to parse.
+ * \param[out] i Pointer to place to store parsed value.
+ * \param[in,out] pos Current position in str, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value_int(
+ const char *str,
+ int64_t *i,
+ size_t *pos)
+{
+ long long temp;
+ char *end = NULL;
+
+ str += *pos;
+ errno = 0;
+ temp = strtoll(str, &end, 0);
+
+ if (end == str || errno == ERANGE ||
+ temp > INT64_MAX || temp < INT64_MIN) {
+ fprintf(stderr, "Failed to parse integer from '%s'\n", str);
+ return false;
+ }
+
+ *i = (int64_t)temp;
+ *pos += (size_t)(end - str);
+ return true;
+}
+
+/**
+ * Parse an unsigned integer value from an argument.
+ *
+ * \param[in] str String containing value to parse.
+ * \param[out] u Pointer to place to store parsed value.
+ * \param[in,out] pos Current position in str, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value_uint(
+ const char *str,
+ uint64_t *u,
+ size_t *pos)
+{
+ unsigned long long temp;
+ char *end = NULL;
+
+ str += *pos;
+ errno = 0;
+ temp = strtoull(str, &end, 0);
+
+ if (end == str || errno == ERANGE || temp > UINT64_MAX) {
+ fprintf(stderr, "Failed to parse unsigned from '%s'\n", str);
+ return false;
+ }
+
+ *u = (uint64_t)temp;
+ *pos += (size_t)(end - str);
+ return true;
+}
+
+/**
+ * Parse an enum value from an argument.
+ *
+ * \param[in] str String containing value to parse.
+ * \param[out] e Enum details.
+ * \param[in,out] pos Current position in str, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value_enum(
+ const char *str,
+ const struct cli_enum *e,
+ size_t *pos)
+{
+ str += *pos;
+ *pos += strlen(str);
+
+ for (const struct cli_str_val *sv = e->desc; sv->str != NULL; sv++) {
+ if (strcmp(str, sv->str) == 0) {
+ *e->e = sv->val;
+ return true;
+ }
+ }
+
+ fprintf(stderr, "ERROR: Unknown enum value '%s'.\n", str);
+
+ return false;
+}
+
+/**
+ * Parse a string value from an argument.
+ *
+ * \param[in] str String containing value to parse.
+ * \param[out] s Pointer to place to store parsed value.
+ * \param[in,out] pos Current position in str, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value_string(
+ const char *str,
+ const char **s,
+ size_t *pos)
+{
+ *s = str + *pos;
+ *pos += strlen(*s);
+ return true;
+}
+
+/**
+ * Parse a value from an argument.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \param[in] arg Argument to parse a value from.
+ * \param[in,out] pos Current position in argument, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value(
+ const struct cli_table_entry *entry,
+ const char *arg,
+ size_t *pos)
+{
+ switch (entry->t) {
+ case CLI_CMD:
+ if (strcmp(arg + *pos, entry->l) == 0) {
+ *pos += strlen(arg);
+ return true;
+ }
+ return false;
+
+ case CLI_INT:
+ return cli__parse_value_int(arg, entry->v.i, pos);
+
+ case CLI_UINT:
+ return cli__parse_value_uint(arg, entry->v.u, pos);
+
+ case CLI_ENUM:
+ return cli__parse_value_enum(arg, &entry->v.e, pos);
+
+ case CLI_STRING:
+ return cli__parse_value_string(arg, entry->v.s, pos);
+
+ default:
+ fprintf(stderr, "Unexpected value for '%s': %s\n",
+ entry->l, arg);
+ break;
+ }
+
+ return false;
+}
+
+/**
+ * Parse a value from an argument.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \param[in] arg_pos Current position in argv.
+ * \param[in,out] pos Current pos in current argument, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_argv_value(const struct cli_table_entry *entry,
+ int argc, const char **argv,
+ int arg_pos, size_t *pos)
+{
+ const char *arg = argv[arg_pos];
+
+ if (arg_pos >= argc) {
+ fprintf(stderr, "Value not given for '%s'\n", entry->l);
+ return false;
+ }
+
+ return cli__parse_value(entry, arg, pos);
+}
+
+/**
+ * Check whether a CLI argument is a positional value.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \return true if the argument is positional, or false otherwise.
+ */
+static inline bool cli__entry_is_positional(const struct cli_table_entry *entry)
+{
+ return entry->p;
+}
+
+/**
+ * Look up a short argument flag.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] s Argument flag to look up in client CLI spec.
+ * \return Client CLI spec entry on success, or NULL otherwise.
+ */
+static const struct cli_table_entry *cli__lookup_short(
+ const struct cli_table *cli, char s)
+{
+ for (size_t i = 0; i < cli->count; i++) {
+ if (cli__entry_is_positional(&cli->entries[i])) {
+ continue;
+ }
+ if (cli->entries[i].s == s) {
+ return &cli->entries[i];
+ }
+ }
+
+ fprintf(stderr, "Unknown flag: '%c'\n", s);
+ return NULL;
+}
+
+/**
+ * Handle an argument with a type that requires a value.
+ *
+ * This can handle the value being in the current argument, optionally split by
+ * a separator, or in the next argument.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \param[in,out] arg_pos Current position in argv, updated on exit.
+ * \param[in] pos Current position in current argument string.
+ * \param[in] sep Name/value separator character, or '\0' if none.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__handle_arg_value(const struct cli_table_entry *entry,
+ int argc, const char **argv, int *arg_pos, size_t pos, char sep)
+{
+ const char *arg = argv[*arg_pos];
+ size_t orig_pos;
+ bool ret;
+
+ if (arg[pos] == '\0') {
+ (*arg_pos)++;
+ pos = 0;
+ } else if (arg[pos] == sep) {
+ pos++;
+ } else if (cli__arg_is_numerical(entry->t) == false) {
+ fprintf(stderr, "Separator required for non-numerical value\n");
+ return false;
+ }
+
+ if (isspace(argv[*arg_pos][pos])) {
+ fprintf(stderr, "Unexpected white space in '%s' "
+ "for argument '%s'\n",
+ &argv[*arg_pos][pos], entry->l);
+ return false;
+ }
+
+ orig_pos = pos;
+ ret = cli__parse_argv_value(entry, argc, argv, *arg_pos, &pos);
+ if (ret != true) {
+ return ret;
+ }
+
+ if (argv[*arg_pos][pos] != '\0') {
+ fprintf(stderr, "Invalid value '%s' for argument '%s'\n",
+ &argv[*arg_pos][orig_pos], entry->l);
+ return false;
+ }
+
+ return true;
+}
+
+static inline bool cli__is_negative(const char *arg)
+{
+ int64_t i;
+ size_t pos = 0;
+
+ return cli__parse_value_int(arg, &i, &pos)
+ && pos == strlen(arg)
+ && i < 0;
+}
+
+/**
+ * Parse a positional argument according to the given CLI spec entry.
+ *
+ * \param[in] ctx Command line interface parsing context.
+ * \param[in] entry Client command line interface argument specification.
+ * \param[in] arg Argument to parse.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_positional_entry(struct cli_ctx *ctx,
+ const struct cli_table_entry *entry,
+ const char *arg)
+{
+ size_t pos = 0;
+ bool ret;
+
+ ret = cli__parse_value(entry, arg, &pos);
+ if (ret != true) {
+ return ret;
+ } else if (arg[pos] != '\0') {
+ fprintf(stderr, "Failed to parse value '%s' for arg '%s'\n",
+ arg, entry->l);
+ return false;
+ }
+
+ ctx->pos_count++;
+ return true;
+}
+
+/**
+ * Parse a positional argument.
+ *
+ * \param[in] ctx Command line interface parsing context.
+ * \param[in] arg Argument to parse.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_positional(struct cli_ctx *ctx,
+ const char *arg)
+{
+ const struct cli_table *cli = ctx->cli;
+ size_t positional = 0;
+
+ for (size_t i = 0; i < cli->count; i++) {
+ if (cli__entry_is_positional(&cli->entries[i])) {
+ if (positional == ctx->pos_count) {
+ return cli__parse_positional_entry(ctx,
+ &cli->entries[i], arg);
+ }
+
+ positional++;
+ }
+ }
+
+ fprintf(stderr, "Unexpected positional argument: '%s'\n", arg);
+ return false;
+}
+
+/**
+ * Parse a flags argument.
+ *
+ * \param[in] ctx Command line interface parsing context.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \param[out] arg_pos Current position in argv, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_short(struct cli_ctx *ctx,
+ int argc, const char **argv, int *arg_pos)
+{
+ const char *arg = argv[*arg_pos];
+ size_t pos = 1;
+
+ if (arg[0] != '-') {
+ return false;
+ }
+
+ while (arg[pos] != '\0') {
+ const struct cli_table_entry *entry;
+
+ entry = cli__lookup_short(ctx->cli, arg[pos]);
+ if (entry == NULL) {
+ if (cli__is_negative(argv[pos])) {
+ return cli__parse_positional(ctx, argv[pos]);
+ }
+ return false;
+ }
+
+ if (entry->no_pos) {
+ ctx->no_pos = true;
+ }
+
+ if (entry->t == CLI_BOOL) {
+ *entry->v.b = true;
+ } else {
+ return cli__handle_arg_value(entry, argc, argv,
+ arg_pos, pos + 1, '\0');
+ }
+
+ pos++;
+ }
+
+ return true;
+}
+
+/**
+ * Look up a long argument name.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] arg Argument name to look up in cli spec.
+ * \param[in,out] pos Current position in arg, updated on exit.
+ * \return Client CLI spec entry on success, or NULL otherwise.
+ */
+static const struct cli_table_entry *cli__lookup_long(
+ const struct cli_table *cli,
+ const char *arg,
+ size_t *pos)
+{
+ arg += *pos;
+
+ for (size_t i = 0; i < cli->count; i++) {
+ if (cli__entry_is_positional(&cli->entries[i]) == false) {
+ const char *name = cli->entries[i].l;
+ size_t name_len = strlen(cli->entries[i].l);
+
+ if (strncmp(name, arg, name_len) == 0) {
+ if (arg[name_len] != '\0' &&
+ arg[name_len] != '=') {
+ continue;
+ }
+ *pos += name_len;
+ return &cli->entries[i];
+ }
+ }
+ }
+
+ fprintf(stderr, "Unknown argument: '%s'\n", arg);
+ return NULL;
+}
+
+/**
+ * Parse a long argument.
+ *
+ * \param[in] ctx Command line interface parsing context.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \param[out] arg_pos Current position in argv, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_long(struct cli_ctx *ctx,
+ int argc, const char **argv, int *arg_pos)
+{
+ const struct cli_table_entry *entry;
+ const char *arg = argv[*arg_pos];
+ size_t pos = 2;
+
+ if (arg[0] != '-' ||
+ arg[1] != '-') {
+ return false;
+ }
+
+ entry = cli__lookup_long(ctx->cli, arg, &pos);
+ if (entry == NULL) {
+ return false;
+ }
+
+ if (entry->no_pos) {
+ ctx->no_pos = true;
+ }
+
+ if (entry->t == CLI_BOOL) {
+ if (arg[pos] != '\0') {
+ fprintf(stderr, "Unexpected value for argument '%s'\n",
+ arg);
+ return false;
+ }
+ *entry->v.b = true;
+ } else {
+ bool ret;
+
+ ret = cli__handle_arg_value(entry, argc, argv,
+ arg_pos, pos, '=');
+ if (ret != true) {
+ return ret;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Get the string to indicate type of value expected for an argument.
+ *
+ * \param[in] type The argument type.
+ * \return String for value type.
+ */
+static const char *cli__string_from_type(enum cli_arg_type type)
+{
+ static const char *const strings[] = {
+ [CLI_BOOL] = "",
+ [CLI_INT] = "INT",
+ [CLI_UINT] = "UINT",
+ [CLI_ENUM] = "ENUM",
+ [CLI_STRING] = "STRING",
+ };
+
+ if (type >= CLI_ARRAY_LEN(strings) || strings[type] == NULL) {
+ return "";
+ }
+
+ return strings[type];
+}
+
+/**
+ * Helper to update a maximum adjusted string length if new values is greater.
+ *
+ * \param[in] str String to check.
+ * \param[in] adjustment Amount to modify length of string by (bytes).
+ * \param[out] len Returns the maximum of existing and this length.
+ */
+static void cli__max_len(const char *str, size_t adjustment, size_t *len)
+{
+ size_t str_len = strlen(str) + adjustment;
+
+ if (str_len > *len) {
+ *len = str_len;
+ }
+}
+
+/**
+ * Count up various properties of the client CLI interface specification.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[out] count Returns number of non-positional arguments.
+ * \param[out] pcount Returns number of positional arguments.
+ * \param[out] max_len Returns max string length of non-positional arguments.
+ * \param[out] pmax_len Returns max string length of positional arguments.
+ * \param[out] phas_desc Returns number of positional args with descriptions.
+ */
+static void cli__count(const struct cli_table *cli,
+ size_t *count,
+ size_t *pcount,
+ size_t *max_len,
+ size_t *pmax_len,
+ size_t *phas_desc)
+{
+ if (count != NULL) *count = 0;
+ if (pcount != NULL) *pcount = 0;
+ if (max_len != NULL) *max_len = 0;
+ if (pmax_len != NULL) *pmax_len = 0;
+ if (phas_desc != NULL) *phas_desc = 0;
+
+ for (size_t i = 0; i < cli->count; i++) {
+ const struct cli_table_entry *entry = &cli->entries[i];
+
+ if (cli__entry_is_positional(entry)) {
+ if (pcount != NULL) {
+ (*pcount)++;
+ }
+ if (pmax_len != NULL) {
+ cli__max_len(entry->l, 0, pmax_len);
+ }
+ if (phas_desc != NULL) {
+ (*phas_desc)++;
+ }
+ } else {
+ if (count != NULL) {
+ (*count)++;
+ }
+ if (max_len != NULL) {
+ const char *type_str;
+ size_t type_len;
+
+ type_str = cli__string_from_type(entry->t);
+ type_len = strlen(type_str);
+
+ cli__max_len(entry->l, type_len, max_len);
+ }
+ }
+ }
+}
+
+/* Documented in cli.h */
+bool cli_parse(const struct cli_table *cli, int argc, const char **argv)
+{
+ struct cli_ctx ctx = {
+ .cli = cli,
+ };
+ enum {
+ ARG_PROG_NAME,
+ ARG_FIRST,
+ };
+
+ for (int i = ARG_FIRST; i < argc; i++) {
+ const char *arg = argv[i];
+ bool ret;
+
+ if (arg[0] == '-') {
+ if (arg[1] == '-') {
+ ret = cli__parse_long(&ctx, argc, argv, &i);
+ } else {
+ ret = cli__parse_short(&ctx, argc, argv, &i);
+ }
+ } else {
+ ret = cli__parse_positional(&ctx, argv[i]);
+ }
+
+ if (ret != true) {
+ return ret;
+ }
+ }
+
+ if (ctx.no_pos == false && ctx.pos_count < cli->min_positional) {
+ fprintf(stderr, "Insufficient positional arguments found.\n");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Get terminal width.
+ *
+ * \return terminal width in characters.
+ */
+static size_t cli__terminal_width(void)
+{
+ return 80;
+}
+
+/**
+ * Print a wrapped string, with a given indent.
+ *
+ * The indent is assumed to already be applied for the first line of the
+ * output by the caller.
+ *
+ * \param[in] str The string to print.
+ * \param[in] indent The number of spaces to pad the left margin with.
+ */
+static void cli__print_wrapping_string(const char *str, size_t indent)
+{
+ size_t terminal_width = cli__terminal_width();
+ size_t avail = (indent > terminal_width) ? 0 : terminal_width - indent;
+ size_t space = avail;
+
+ while (*str != '\0') {
+ size_t word_len = strcspn(str, " \n\t");
+ if (word_len <= space || space == avail) {
+ fprintf(stderr, "%*.*s",
+ (int)word_len,
+ (int)word_len, str);
+ str += word_len;
+ if (word_len <= space) {
+ space -= word_len;
+ }
+ if (space > 0) {
+ fprintf(stderr, " ");
+ space--;
+ }
+ } else {
+ fprintf(stderr, "\n%*s", (int)indent, "");
+ space = avail;
+ }
+ str += strspn(str, " \n\t");
+ }
+}
+
+/**
+ * Print an entry's description, with a given indent.
+ *
+ * The indent is assumed to already be applied for the first line of the
+ * output by the caller.
+ *
+ * \param[in] entry The entry to print the description for.
+ * \param[in] indent The number of spaces to pad the left margin with.
+ */
+static void cli__print_description(const struct cli_table_entry *entry,
+ size_t indent)
+{
+ if (entry->d != NULL) {
+ cli__print_wrapping_string(entry->d, indent);
+ }
+
+ fprintf(stderr, "\n");
+
+ if (entry->t == CLI_ENUM) {
+ size_t max_len = 0;
+
+ for (const struct cli_str_val *e = entry->v.e.desc;
+ e->str != NULL; e++) {
+ size_t len = strlen(e->str);
+ if (max_len < len) {
+ max_len = len;
+ }
+ }
+
+ fprintf(stderr, "\n");
+
+ for (const struct cli_str_val *e = entry->v.e.desc;
+ e->str != NULL; e++) {
+ fprintf(stderr, " ");
+
+ if (e->d == NULL || e->d[0] == '\0') {
+ fprintf(stderr, "%s\n",
+ e->str);
+ } else {
+ fprintf(stderr, "%-*s - ",
+ (int)(max_len),
+ e->str);
+ cli__print_wrapping_string(e->d,
+ 8 + max_len + 3);
+ fprintf(stderr, "\n");
+ }
+ }
+ }
+}
+
+/* Documented in cli.h */
+void cli_help(const struct cli_table *cli, const char *prog_name)
+{
+ size_t count;
+ size_t pcount;
+ size_t max_len;
+ size_t pmax_len;
+ size_t phas_desc;
+ size_t required = 0;
+ enum {
+ ARG_PROG_NAME,
+ };
+
+ cli__count(cli, &count, &pcount, &max_len, &pmax_len, &phas_desc);
+
+ if (cli->d != NULL) {
+ fprintf(stderr, "\n");
+ cli__print_wrapping_string(cli->d, 0);
+ fprintf(stderr, "\n");
+ }
+
+ fprintf(stderr, "\nUsage: %s", prog_name);
+
+ if (pcount > 0) {
+ for (size_t i = 0; i < cli->count; i++) {
+ if (cli__entry_is_positional(&cli->entries[i])) {
+ const char *punctuation =
+ (required == cli->min_positional) ?
+ " [" : " ";
+
+ if (cli->entries[i].t == CLI_CMD) {
+ fprintf(stderr, "%s%s", punctuation,
+ cli->entries[i].l);
+ } else {
+ fprintf(stderr, "%s<%s>", punctuation,
+ cli->entries[i].l);
+ }
+ required++;
+ }
+ }
+ if (required == pcount && required > cli->min_positional) {
+ fprintf(stderr, "]");
+ }
+ }
+
+ if (count > 0) {
+ fprintf(stderr, " [options]");
+ }
+
+ fprintf(stderr, "\n\n");
+
+ if (phas_desc > 0) {
+ fprintf(stderr, "Where:\n\n");
+
+ for (size_t i = 0; i < cli->count; i++) {
+ const struct cli_table_entry *entry = &cli->entries[i];
+
+ if (entry->d == NULL) {
+ continue;
+ }
+
+ if (cli__entry_is_positional(entry)) {
+ fprintf(stderr, " %*.*s ",
+ (int)pmax_len,
+ (int)pmax_len,
+ entry->l);
+ cli__print_description(entry, pmax_len + 4);
+ fprintf(stderr, "\n");
+ }
+ }
+ }
+
+ if (count > 0) {
+ fprintf(stderr, "Options:\n\n");
+
+ for (size_t i = 0; i < cli->count; i++) {
+ const struct cli_table_entry *entry = &cli->entries[i];
+ const char *type_str;
+ size_t type_len;
+ size_t arg_len;
+
+ if (cli__entry_is_positional(entry)) {
+ continue;
+ }
+
+ if (entry->s != '\0') {
+ fprintf(stderr, " -%c", entry->s);
+ } else {
+ fprintf(stderr, " ");
+ }
+
+ type_str = cli__string_from_type(entry->t);
+ type_len = strlen(type_str);
+ arg_len = strlen(entry->l);
+
+ fprintf(stderr, " --%s %s%*.s ", entry->l, type_str,
+ (int)(max_len - arg_len - type_len),
+ "");
+ cli__print_description(entry, max_len + 11);
+ fprintf(stderr, "\n");
+ }
+ }
+}
diff --git a/test/cli.h b/test/cli.h
new file mode 100644
index 0000000..ffcd272
--- /dev/null
+++ b/test/cli.h
@@ -0,0 +1,99 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (C) 2021-2022 Michael Drake <tlsa@netsurf-browser.org>
+ */
+
+/**
+ * \file
+ * \brief Command line argument handling API.
+ */
+
+#ifndef _PELTAR_CLI_H_
+#define _PELTAR_CLI_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/**
+ * Helper to get element count for an array,
+ *
+ * \param[in] _a Array to get number of elements for.
+ */
+#define CLI_ARRAY_LEN(_a) ((sizeof(_a))/(sizeof(*(_a))))
+
+/**
+ * CLI argument type.
+ */
+enum cli_arg_type {
+ CLI_CMD, /**< A sub-command. Must match long argument name. */
+ CLI_BOOL, /**< Has no value; presence of flag indicates true. */
+ CLI_INT, /**< Has signed integer value. */
+ CLI_UINT, /**< Has unsigned integer value. */
+ CLI_ENUM, /**< Has enumeration value. */
+ CLI_STRING, /**< Has string value. */
+};
+
+/** Enum value descriptor. */
+struct cli_str_val {
+ const char *str; /**< String for the enum value name. */
+ int64_t val; /**< The value for this string. */
+ const char *d; /**< Description of this value for help output. */
+};
+
+/** Enum data. */
+struct cli_enum {
+ const struct cli_str_val *desc; /**< Array describing enum values. */
+ int64_t *e; /**< Location to store \ref CLI_ENUM value. */
+};
+
+/**
+ * Client description for a command line argument.
+ */
+struct cli_table_entry {
+ const char *l; /**< Long argument name. */
+ const char s; /**< Short flag name. (Non-positional arguments.) */
+ bool p; /**< Whether the argument is a positional argument. */
+ bool no_pos; /**< When present, no positional arguments are required. */
+ enum cli_arg_type t; /**< Argument type. */
+ union {
+ bool *b; /**< Location to store \ref CLI_BOOL value. */
+ int64_t *i; /**< Location to store \ref CLI_INT value. */
+ uint64_t *u; /**< Location to store \ref CLI_UINT value. */
+ const char **s; /**< Location to store \ref CLI_STRING value. */
+ struct cli_enum e; /**< \ref CLI_ENUM value details. */
+ } v; /**< Where to store type-specific values. */
+ const char *d; /**< Description of this argument for help output. */
+};
+
+/**
+ * Client command line interface specification.
+ */
+struct cli_table {
+ const struct cli_table_entry *entries;
+ size_t count;
+ size_t min_positional;
+ const char *d; /**< Description of this application for help output. */
+};
+
+/**
+ * Parse the command line arguments.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \return true on success, false on error.
+ */
+bool cli_parse(const struct cli_table *cli, int argc, const char **argv);
+
+/**
+ * Print usage and help output.
+ *
+ * Note: Assumes non-Unicode. (One byte per character.)
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] prog_name Program name.
+ */
+void cli_help(const struct cli_table *cli, const char *prog_name);
+
+#endif
diff --git a/test/decode_gif.c b/test/decode_gif.c
deleted file mode 100644
index 619be29..0000000
--- a/test/decode_gif.c
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * Copyright 2008 Sean Fox <dyntryx@gmail.com>
- * Copyright 2008 James Bursa <james@netsurf-browser.org>
- *
- * This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
- * Licenced under the MIT License,
- * http://www.opensource.org/licenses/mit-license.php
- */
-
-#include <assert.h>
-#include <errno.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/stat.h>
-
-#include "../include/libnsgif.h"
-
-#define BYTES_PER_PIXEL 4
-#define MAX_IMAGE_BYTES (48 * 1024 * 1024)
-
-
-static void *bitmap_create(int width, int height)
-{
- /* ensure a stupidly large bitmap is not created */
- if (((long long)width * (long long)height) > (MAX_IMAGE_BYTES/BYTES_PER_PIXEL)) {
- return NULL;
- }
- return calloc(width * height, BYTES_PER_PIXEL);
-}
-
-
-static void bitmap_set_opaque(void *bitmap, bool opaque)
-{
- (void) opaque; /* unused */
- (void) bitmap; /* unused */
- assert(bitmap);
-}
-
-
-static bool bitmap_test_opaque(void *bitmap)
-{
- (void) bitmap; /* unused */
- assert(bitmap);
- return false;
-}
-
-
-static unsigned char *bitmap_get_buffer(void *bitmap)
-{
- assert(bitmap);
- return bitmap;
-}
-
-
-static void bitmap_destroy(void *bitmap)
-{
- assert(bitmap);
- free(bitmap);
-}
-
-
-static void bitmap_modified(void *bitmap)
-{
- (void) bitmap; /* unused */
- assert(bitmap);
- return;
-}
-
-static unsigned char *load_file(const char *path, size_t *data_size)
-{
- FILE *fd;
- struct stat sb;
- unsigned char *buffer;
- size_t size;
- size_t n;
-
- fd = fopen(path, "rb");
- if (!fd) {
- perror(path);
- exit(EXIT_FAILURE);
- }
-
- if (stat(path, &sb)) {
- perror(path);
- exit(EXIT_FAILURE);
- }
- size = sb.st_size;
-
- buffer = malloc(size);
- if (!buffer) {
- fprintf(stderr, "Unable to allocate %lld bytes\n",
- (long long) size);
- exit(EXIT_FAILURE);
- }
-
- n = fread(buffer, 1, size, fd);
- if (n != size) {
- perror(path);
- exit(EXIT_FAILURE);
- }
-
- fclose(fd);
-
- *data_size = size;
- return buffer;
-}
-
-
-static void warning(const char *context, gif_result code)
-{
- fprintf(stderr, "%s failed: ", context);
- switch (code)
- {
- case GIF_INSUFFICIENT_FRAME_DATA:
- fprintf(stderr, "GIF_INSUFFICIENT_FRAME_DATA");
- break;
- case GIF_FRAME_DATA_ERROR:
- fprintf(stderr, "GIF_FRAME_DATA_ERROR");
- break;
- case GIF_INSUFFICIENT_DATA:
- fprintf(stderr, "GIF_INSUFFICIENT_DATA");
- break;
- case GIF_DATA_ERROR:
- fprintf(stderr, "GIF_DATA_ERROR");
- break;
- case GIF_INSUFFICIENT_MEMORY:
- fprintf(stderr, "GIF_INSUFFICIENT_MEMORY");
- break;
- default:
- fprintf(stderr, "unknown code %i", code);
- break;
- }
- fprintf(stderr, "\n");
-}
-
-static void write_ppm(FILE* fh, const char *name, gif_animation *gif,
- bool no_write)
-{
- unsigned int i;
- gif_result code;
-
- if (!no_write) {
- fprintf(fh, "P3\n");
- fprintf(fh, "# %s\n", name);
- fprintf(fh, "# width %u \n", gif->width);
- fprintf(fh, "# height %u \n", gif->height);
- fprintf(fh, "# frame_count %u \n", gif->frame_count);
- fprintf(fh, "# frame_count_partial %u \n", gif->frame_count_partial);
- fprintf(fh, "# loop_count %u \n", gif->loop_count);
- fprintf(fh, "%u %u 256\n", gif->width, gif->height * gif->frame_count);
- }
-
- /* decode the frames */
- for (i = 0; i != gif->frame_count; i++) {
- unsigned int row, col;
- unsigned char *image;
-
- code = gif_decode_frame(gif, i);
- if (code != GIF_OK)
- warning("gif_decode_frame", code);
-
- if (!no_write) {
- fprintf(fh, "# frame %u:\n", i);
- image = (unsigned char *) gif->frame_image;
- for (row = 0; row != gif->height; row++) {
- for (col = 0; col != gif->width; col++) {
- size_t z = (row * gif->width + col) * 4;
- fprintf(fh, "%u %u %u ",
- (unsigned char) image[z],
- (unsigned char) image[z + 1],
- (unsigned char) image[z + 2]);
- }
- fprintf(fh, "\n");
- }
- }
- }
-
-}
-
-int main(int argc, char *argv[])
-{
- gif_bitmap_callback_vt bitmap_callbacks = {
- bitmap_create,
- bitmap_destroy,
- bitmap_get_buffer,
- bitmap_set_opaque,
- bitmap_test_opaque,
- bitmap_modified
- };
- gif_animation gif;
- size_t size;
- gif_result code;
- unsigned char *data;
- FILE *outf = stdout;
- bool no_write = false;
-
- if (argc < 2) {
- fprintf(stderr, "Usage: %s image.gif [out]\n", argv[0]);
- fprintf(stderr, "\n");
- fprintf(stderr, "If [out] is NOWRITE, the gif will be docoded "
- "but not output.\n");
- fprintf(stderr, "Otherwise [out] is an output filename.\n");
- fprintf(stderr, "When [out] is unset, output is to stdout.\n");
-
- return 1;
- }
-
- if (argc > 2) {
- if (strcmp(argv[2], "NOWRITE") == 0) {
- no_write = true;
- } else {
- outf = fopen(argv[2], "w+");
- if (outf == NULL) {
- fprintf(stderr, "Unable to open %s for writing\n", argv[2]);
- return 2;
- }
- }
- }
-
- /* create our gif animation */
- gif_create(&gif, &bitmap_callbacks);
-
- /* load file into memory */
- data = load_file(argv[1], &size);
-
- /* begin decoding */
- do {
- code = gif_initialise(&gif, size, data);
- if (code != GIF_OK && code != GIF_WORKING) {
- warning("gif_initialise", code);
- gif_finalise(&gif);
- free(data);
- return 1;
- }
- } while (code != GIF_OK);
-
- write_ppm(outf, argv[1], &gif, no_write);
-
- if (argc > 2 && !no_write) {
- fclose(outf);
- }
-
- /* clean up */
- gif_finalise(&gif);
- free(data);
-
- return 0;
-}
diff --git a/test/nsgif.c b/test/nsgif.c
new file mode 100644
index 0000000..29341e7
--- /dev/null
+++ b/test/nsgif.c
@@ -0,0 +1,442 @@
+/*
+ * Copyright 2008 Sean Fox <dyntryx@gmail.com>
+ * Copyright 2008 James Bursa <james@netsurf-browser.org>
+ * Copyright 2022 Michael Drake <tlsa@netsurf-browser.org>
+ *
+ * This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
+ * Licenced under the MIT License,
+ * http://www.opensource.org/licenses/mit-license.php
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "../include/nsgif.h"
+
+#include "cli.h"
+#include "cli.c"
+
+#define STR_VAL(_S) STR(_S)
+#define STR(_S) #_S
+
+#define BYTES_PER_PIXEL 4
+
+static struct nsgif_options {
+ const char *file;
+ const char *ppm;
+ uint64_t loops;
+ bool palette;
+ bool version;
+ bool info;
+ bool help;
+} nsgif_options;
+
+static const struct cli_table_entry cli_entries[] = {
+ {
+ .s = 'h',
+ .l = "help",
+ .t = CLI_BOOL,
+ .no_pos = true,
+ .v.b = &nsgif_options.help,
+ .d = "Print this text.",
+ },
+ {
+ .s = 'i',
+ .l = "info",
+ .t = CLI_BOOL,
+ .v.b = &nsgif_options.info,
+ .d = "Dump GIF info to stdout."
+ },
+ {
+ .s = 'l',
+ .l = "loops",
+ .t = CLI_UINT,
+ .v.u = &nsgif_options.loops,
+ .d = "Loop through decoding all frames N times. "
+ "The default is 1."
+ },
+ {
+ .s = 'm',
+ .l = "ppm",
+ .t = CLI_STRING,
+ .v.s = &nsgif_options.ppm,
+ .d = "Convert frames to PPM image at given path."
+ },
+ {
+ .s = 'p',
+ .l = "palette",
+ .t = CLI_BOOL,
+ .v.b = &nsgif_options.palette,
+ .d = "Save palette images."
+ },
+ {
+ .s = 'V',
+ .l = "version",
+ .t = CLI_BOOL,
+ .no_pos = true,
+ .v.b = &nsgif_options.version,
+ .d = "Print version number."
+ },
+ {
+ .p = true,
+ .l = "FILE",
+ .t = CLI_STRING,
+ .v.s = &nsgif_options.file,
+ .d = "Path to GIF file to load."
+ },
+};
+
+const struct cli_table cli = {
+ .entries = cli_entries,
+ .count = (sizeof(cli_entries))/(sizeof(*cli_entries)),
+ .min_positional = 1,
+ .d = "NSGIF - A utility for inspecting and decoding GIFs with libnsgif",
+};
+
+static void *bitmap_create(int width, int height)
+{
+ /* Ensure a stupidly large bitmap is not created */
+ if (width > 4096 || height > 4096) {
+ return NULL;
+ }
+
+ return calloc(width * height, BYTES_PER_PIXEL);
+}
+
+static unsigned char *bitmap_get_buffer(void *bitmap)
+{
+ return bitmap;
+}
+
+static void bitmap_destroy(void *bitmap)
+{
+ free(bitmap);
+}
+
+static uint8_t *load_file(const char *path, size_t *data_size)
+{
+ FILE *fd;
+ struct stat sb;
+ unsigned char *buffer;
+ size_t size;
+ size_t n;
+
+ fd = fopen(path, "rb");
+ if (!fd) {
+ perror(path);
+ exit(EXIT_FAILURE);
+ }
+
+ if (stat(path, &sb)) {
+ perror(path);
+ exit(EXIT_FAILURE);
+ }
+ size = sb.st_size;
+
+ buffer = malloc(size);
+ if (!buffer) {
+ fprintf(stderr, "Unable to allocate %lld bytes\n",
+ (long long) size);
+ exit(EXIT_FAILURE);
+ }
+
+ n = fread(buffer, 1, size, fd);
+ if (n != size) {
+ perror(path);
+ exit(EXIT_FAILURE);
+ }
+
+ fclose(fd);
+
+ *data_size = size;
+ return buffer;
+}
+
+static void warning(const char *context, nsgif_error err)
+{
+ fprintf(stderr, "%s: %s\n", context, nsgif_strerror(err));
+}
+
+static void print_gif_info(const nsgif_info_t *info)
+{
+ const uint8_t *bg = (uint8_t *) &info->background;
+
+ fprintf(stdout, "gif:\n");
+ fprintf(stdout, " width: %"PRIu32"\n", info->width);
+ fprintf(stdout, " height: %"PRIu32"\n", info->height);
+ fprintf(stdout, " max-loops: %"PRIu32"\n", info->loop_max);
+ fprintf(stdout, " frame-count: %"PRIu32"\n", info->frame_count);
+ fprintf(stdout, " global palette: %s\n", info->global_palette ? "yes" : "no");
+ fprintf(stdout, " background:\n");
+ fprintf(stdout, " red: 0x%"PRIx8"\n", bg[0]);
+ fprintf(stdout, " green: 0x%"PRIx8"\n", bg[1]);
+ fprintf(stdout, " blue: 0x%"PRIx8"\n", bg[2]);
+ fprintf(stdout, " frames:\n");
+}
+
+static void print_gif_frame_info(const nsgif_frame_info_t *info, uint32_t i)
+{
+ const char *disposal = nsgif_str_disposal(info->disposal);
+
+ fprintf(stdout, " - frame: %"PRIu32"\n", i);
+ fprintf(stdout, " local palette: %s\n", info->local_palette ? "yes" : "no");
+ fprintf(stdout, " disposal-method: %s\n", disposal);
+ fprintf(stdout, " transparency: %s\n", info->transparency ? "yes" : "no");
+ fprintf(stdout, " interlaced: %s\n", info->interlaced ? "yes" : "no");
+ fprintf(stdout, " display: %s\n", info->display ? "yes" : "no");
+ fprintf(stdout, " delay: %"PRIu32"\n", info->delay);
+ fprintf(stdout, " rect:\n");
+ fprintf(stdout, " x: %"PRIu32"\n", info->rect.x0);
+ fprintf(stdout, " y: %"PRIu32"\n", info->rect.y0);
+ fprintf(stdout, " w: %"PRIu32"\n", info->rect.x1 - info->rect.x0);
+ fprintf(stdout, " h: %"PRIu32"\n", info->rect.y1 - info->rect.y0);
+}
+
+static bool save_palette(
+ const char *img_filename,
+ const char *palette_filename,
+ const uint32_t palette[NSGIF_MAX_COLOURS],
+ size_t used_entries)
+{
+ enum {
+ SIZE = 32,
+ COUNT = 16,
+ };
+ FILE *f;
+ int size = COUNT * SIZE + 1;
+
+ f = fopen(palette_filename, "w+");
+ if (f == NULL) {
+ fprintf(stderr, "Unable to open %s for writing\n",
+ palette_filename);
+ return false;
+ }
+
+ fprintf(f, "P3\n");
+ fprintf(f, "# %s: %s\n", img_filename, palette_filename);
+ fprintf(f, "# Colour count: %zu\n", used_entries);
+ fprintf(f, "%u %u 256\n", size, size);
+
+ for (int y = 0; y < size; y++) {
+ for (int x = 0; x < size; x++) {
+ if (x % SIZE == 0 || y % SIZE == 0) {
+ fprintf(f, "0 0 0 ");
+ } else {
+ size_t offset = y / SIZE * COUNT + x / SIZE;
+ uint8_t *entry = (uint8_t *)&palette[offset];
+
+ fprintf(f, "%u %u %u ",
+ entry[0],
+ entry[1],
+ entry[2]);
+ }
+ }
+
+ fprintf(f, "\n");
+ }
+
+ fclose(f);
+
+ return true;
+}
+
+static bool save_global_palette(const nsgif_t *gif)
+{
+ uint32_t table[NSGIF_MAX_COLOURS];
+ size_t entries;
+
+ nsgif_global_palette(gif, table, &entries);
+
+ return save_palette(nsgif_options.file, "global-palette.ppm",
+ table, entries);
+}
+
+static bool save_local_palette(const nsgif_t *gif, uint32_t frame)
+{
+ static uint32_t table[NSGIF_MAX_COLOURS];
+ char filename[64];
+ size_t entries;
+
+ snprintf(filename, sizeof(filename), "local-palette-%"PRIu32".ppm",
+ frame);
+
+ if (!nsgif_local_palette(gif, frame, table, &entries)) {
+ return false;
+ }
+
+ return save_palette(nsgif_options.file, filename, table, entries);
+}
+
+static void decode(FILE* ppm, const char *name, nsgif_t *gif, bool first)
+{
+ nsgif_error err;
+ uint32_t frame_prev = 0;
+ const nsgif_info_t *info;
+
+ info = nsgif_get_info(gif);
+
+ if (first && ppm != NULL) {
+ fprintf(ppm, "P3\n");
+ fprintf(ppm, "# %s\n", name);
+ fprintf(ppm, "# width %u \n", info->width);
+ fprintf(ppm, "# height %u \n", info->height);
+ fprintf(ppm, "# frame_count %u \n", info->frame_count);
+ fprintf(ppm, "# loop_max %u \n", info->loop_max);
+ fprintf(ppm, "%u %u 256\n", info->width,
+ info->height * info->frame_count);
+ }
+
+ if (first && nsgif_options.info) {
+ print_gif_info(info);
+ }
+ if (first && nsgif_options.palette && info->global_palette) {
+ save_global_palette(gif);
+ }
+
+ /* decode the frames */
+ while (true) {
+ nsgif_bitmap_t *bitmap;
+ const uint8_t *image;
+ uint32_t frame_new;
+ uint32_t delay_cs;
+ nsgif_rect_t area;
+
+ err = nsgif_frame_prepare(gif, &area,
+ &delay_cs, &frame_new);
+ if (err != NSGIF_OK) {
+ warning("nsgif_frame_prepare", err);
+ return;
+ }
+
+ if (frame_new < frame_prev) {
+ /* Must be an animation that loops. We only care about
+ * decoding each frame once in this utility. */
+ return;
+ }
+ frame_prev = frame_new;
+
+ if (first && nsgif_options.info) {
+ const nsgif_frame_info_t *f_info;
+
+ f_info = nsgif_get_frame_info(gif, frame_new);
+ if (f_info != NULL) {
+ print_gif_frame_info(f_info, frame_new);
+ }
+ }
+ if (first && nsgif_options.palette) {
+ save_local_palette(gif, frame_new);
+ }
+
+ err = nsgif_frame_decode(gif, frame_new, &bitmap);
+ if (err != NSGIF_OK) {
+ fprintf(stderr, "Frame %"PRIu32": "
+ "nsgif_decode_frame failed: %s\n",
+ frame_new, nsgif_strerror(err));
+ /* Continue decoding the rest of the frames. */
+
+ } else if (first && ppm != NULL) {
+ fprintf(ppm, "# frame %u:\n", frame_new);
+ image = (const uint8_t *) bitmap;
+ for (uint32_t y = 0; y != info->height; y++) {
+ for (uint32_t x = 0; x != info->width; x++) {
+ size_t z = (y * info->width + x) * 4;
+ fprintf(ppm, "%u %u %u ",
+ image[z],
+ image[z + 1],
+ image[z + 2]);
+ }
+ fprintf(ppm, "\n");
+ }
+ }
+
+ if (delay_cs == NSGIF_INFINITE) {
+ /** This frame is the last. */
+ return;
+ }
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ const nsgif_bitmap_cb_vt bitmap_callbacks = {
+ .create = bitmap_create,
+ .destroy = bitmap_destroy,
+ .get_buffer = bitmap_get_buffer,
+ };
+ size_t size;
+ nsgif_t *gif;
+ uint8_t *data;
+ nsgif_error err;
+ FILE *ppm = NULL;
+
+ /* Override default options with any command line args */
+ if (!cli_parse(&cli, argc, (void *)argv)) {
+ cli_help(&cli, argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ if (nsgif_options.help) {
+ cli_help(&cli, argv[0]);
+ return EXIT_SUCCESS;
+ }
+
+ if (nsgif_options.version) {
+ printf("%s %s\n", STR_VAL(NSGIF_NAME), STR_VAL(NSGIF_VERSION));
+ return EXIT_SUCCESS;
+ }
+
+ if (nsgif_options.ppm != NULL) {
+ ppm = fopen(nsgif_options.ppm, "w+");
+ if (ppm == NULL) {
+ fprintf(stderr, "Unable to open %s for writing\n",
+ nsgif_options.ppm);
+ return EXIT_FAILURE;
+ }
+ }
+
+ /* create our gif animation */
+ err = nsgif_create(&bitmap_callbacks, NSGIF_BITMAP_FMT_R8G8B8A8, &gif);
+ if (err != NSGIF_OK) {
+ warning("nsgif_create", err);
+ return EXIT_FAILURE;
+ }
+
+ /* load file into memory */
+ data = load_file(nsgif_options.file, &size);
+
+ /* Scan the raw data */
+ err = nsgif_data_scan(gif, size, data);
+ if (err != NSGIF_OK) {
+ /* Not fatal; some GIFs are nasty. Can still try to decode
+ * any frames that were decoded successfully. */
+ warning("nsgif_data_scan", err);
+ }
+
+ nsgif_data_complete(gif);
+
+ if (nsgif_options.loops == 0) {
+ nsgif_options.loops = 1;
+ }
+
+ for (uint64_t i = 0; i < nsgif_options.loops; i++) {
+ decode(ppm, nsgif_options.file, gif, i == 0);
+
+ /* We want to ignore any loop limit in the GIF. */
+ nsgif_reset(gif);
+ }
+
+ if (ppm != NULL) {
+ fclose(ppm);
+ }
+
+ /* clean up */
+ nsgif_destroy(gif);
+ free(data);
+
+ return 0;
+}
diff --git a/test/runtest.sh b/test/runtest.sh
index fd5a32b..fd84847 100755
--- a/test/runtest.sh
+++ b/test/runtest.sh
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/sh
# run test images through libnsgif and count results
@@ -20,25 +20,25 @@ GIFTESTS="${GIFTESTS} test/ns-afl-gif/*.gif"
gifdecode()
{
- OUTF=$(basename ${1} .gif)
- CMPF=$(dirname ${1})/${OUTF}.ppm
- echo "GIF:${1}" >> ${TEST_LOG}
- ${TEST_PATH}/test_decode_gif ${1} ${TEST_OUT}/${OUTF}.ppm 2>> ${TEST_LOG}
- ECODE=$?
-
- echo "Exit code:${ECODE}" >> ${TEST_LOG}
- if [ "${ECODE}" -gt 0 ];then
- return ${ECODE}
- fi
-
- if [ -f "${CMPF}" ]; then
- cmp ${CMPF} ${TEST_OUT}/${OUTF}.ppm >> ${TEST_LOG} 2>> ${TEST_LOG}
- if [ "$?" -ne 0 ]; then
- return 128
+ OUTF=$(basename ${1} .gif)
+ CMPF=$(dirname ${1})/${OUTF}.ppm
+ echo "GIF:${1}" >> ${TEST_LOG}
+ ${TEST_PATH}/test_nsgif ${1} --ppm ${TEST_OUT}/${OUTF}.ppm 2>> ${TEST_LOG}
+ ECODE=$?
+
+ echo "Exit code:${ECODE}" >> ${TEST_LOG}
+ if [ "${ECODE}" -gt 0 ];then
+ return ${ECODE}
fi
- fi
- return 0
+ if [ -f "${CMPF}" ]; then
+ cmp ${CMPF} ${TEST_OUT}/${OUTF}.ppm >> ${TEST_LOG} 2>> ${TEST_LOG}
+ if [ "$?" -ne 0 ]; then
+ return 128
+ fi
+ fi
+
+ return 0
}
GIFTESTTOTC=0
@@ -49,25 +49,27 @@ GIFTESTERRC=0
echo "Testing GIF decode"
for GIF in $(ls ${GIFTESTS});do
- GIFTESTTOTC=$((GIFTESTTOTC+1))
- gifdecode ${GIF}
- ECODE=$?
- if [ "${ECODE}" -gt 127 ];then
- GIFTESTERRC=$((GIFTESTERRC+1))
- else
- if [ "${ECODE}" -gt 0 ];then
- GIFTESTFAILC=$((GIFTESTFAILC+1))
+ GIFTESTTOTC=$((GIFTESTTOTC+1))
+ #echo "${GIF}"
+ gifdecode ${GIF}
+ ECODE=$?
+ if [ "${ECODE}" -gt 127 ];then
+ GIFTESTERRC=$((GIFTESTERRC+1))
+ echo "Error ${GIF}"
else
- GIFTESTPASSC=$((GIFTESTPASSC+1))
+ if [ "${ECODE}" -gt 0 ];then
+ GIFTESTFAILC=$((GIFTESTFAILC+1))
+ else
+ GIFTESTPASSC=$((GIFTESTPASSC+1))
+ fi
fi
- fi
done
echo "Tests:${GIFTESTTOTC} Pass:${GIFTESTPASSC} Fail:${GIFTESTFAILC} Error:${GIFTESTERRC}"
# exit code
if [ "${GIFTESTERRC}" -gt 0 ]; then
- exit 1
+ exit 1
fi
exit 0