]> granicus.if.org Git - neomutt/commitdiff
add enum type
authorRichard Russon <rich@flatcap.org>
Sun, 9 Jun 2019 14:49:03 +0000 (15:49 +0100)
committerRichard Russon <rich@flatcap.org>
Wed, 12 Jun 2019 23:23:35 +0000 (00:23 +0100)
Create a new config type for enumerations.
This will use the `ConfigDef.data` field and an `EnumDef` to define the
allowed strings.

Makefile.autosetup
config/enum.c [new file with mode: 0644]
config/enum.h [new file with mode: 0644]
config/lib.h
init.c
po/POTFILES.in
test/Makefile.autosetup
test/config/enum.c [new file with mode: 0644]
test/config/enum.h [new file with mode: 0644]
test/main.c

index 32d49a10924daf2229040bd44020bc2ed2f7745d..a17276d019acc7a3b4e9943d4a6cf7a6ede4cb4a 100644 (file)
@@ -227,7 +227,7 @@ ALLOBJS+=   $(PGPEWRAPOBJS)
 ###############################################################################
 # libconfig
 LIBCONFIG=     libconfig.a
-LIBCONFIGOBJS= config/address.o config/bool.o config/dump.o \
+LIBCONFIGOBJS= config/address.o config/bool.o config/dump.o config/enum.o \
                config/long.o config/magic.o config/mbtable.o config/number.o \
                config/quad.o config/regex.o config/set.o \
                config/sort.o config/string.o
diff --git a/config/enum.c b/config/enum.c
new file mode 100644 (file)
index 0000000..812f521
--- /dev/null
@@ -0,0 +1,235 @@
+/**
+ * @file
+ * Type representing an enumeration
+ *
+ * @authors
+ * Copyright (C) 2018 Richard Russon <rich@flatcap.org>
+ *
+ * @copyright
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @page config_enum Type: Enumeration
+ *
+ * Type representing an enumeration.
+ */
+
+#include "config.h"
+#include <stddef.h>
+#include <limits.h>
+#include <stdint.h>
+#include "mutt/buffer.h"
+#include "mutt/hash.h"
+#include "mutt/logging.h"
+#include "mutt/mapping.h"
+#include "mutt/memory.h"
+#include "mutt/string2.h"
+#include "enum.h"
+#include "set.h"
+#include "types.h"
+
+/**
+ * enum_string_set - Set a Enumeration by string
+ * @param cs    Config items
+ * @param var   Variable to set
+ * @param cdef  Variable definition
+ * @param value Value to set
+ * @param err   Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ *
+ * If var is NULL, then the config item's initial value will be set.
+ */
+static int enum_string_set(const struct ConfigSet *cs, void *var, struct ConfigDef *cdef,
+                           const char *value, struct Buffer *err)
+{
+  if (!cs || !cdef || !value)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  struct EnumDef *ed = (struct EnumDef *) cdef->data;
+  if (!ed || !ed->lookup)
+    return CSR_ERR_CODE;
+
+  int num = mutt_map_get_value(value, ed->lookup);
+  if (num < 0)
+  {
+    mutt_buffer_printf(err, "Invalid enum value: %s", value);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  if (var)
+  {
+    if (num == (*(unsigned char *) var))
+      return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+    if (cdef->validator)
+    {
+      int rc = cdef->validator(cs, cdef, (intptr_t) num, err);
+
+      if (CSR_RESULT(rc) != CSR_SUCCESS)
+        return (rc | CSR_INV_VALIDATOR);
+    }
+
+    *(unsigned char *) var = num;
+  }
+  else
+  {
+    cdef->initial = num;
+  }
+
+  return CSR_SUCCESS;
+}
+
+/**
+ * enum_string_get - Get a Enumeration as a string
+ * @param cs     Config items
+ * @param var    Variable to get
+ * @param cdef   Variable definition
+ * @param result Buffer for results or error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ *
+ * If var is NULL, then the config item's initial value will be returned.
+ */
+static int enum_string_get(const struct ConfigSet *cs, void *var,
+                           const struct ConfigDef *cdef, struct Buffer *result)
+{
+  if (!cs || !cdef)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  unsigned int value;
+
+  if (var)
+    value = *(unsigned char *) var;
+  else
+    value = (int) cdef->initial;
+
+  struct EnumDef *ed = (struct EnumDef *) cdef->data;
+  if (!ed || !ed->lookup)
+    return CSR_ERR_CODE;
+
+  const char *name = mutt_map_get_name(value, ed->lookup);
+  if (!name)
+  {
+    mutt_debug(1, "Variable has an invalid value: %d\n", value);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  mutt_buffer_addstr(result, name);
+  return CSR_SUCCESS;
+}
+
+/**
+ * enum_native_set - Set a Enumeration config item by int
+ * @param cs    Config items
+ * @param var   Variable to set
+ * @param cdef  Variable definition
+ * @param value Enumeration value
+ * @param err   Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+static int enum_native_set(const struct ConfigSet *cs, void *var,
+                           const struct ConfigDef *cdef, intptr_t value, struct Buffer *err)
+{
+  if (!cs || !var || !cdef)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  struct EnumDef *ed = (struct EnumDef *) cdef->data;
+  if (!ed || !ed->lookup)
+    return CSR_ERR_CODE;
+
+  const char *name = mutt_map_get_name(value, ed->lookup);
+  if (!name)
+  {
+    mutt_buffer_printf(err, "Invalid enum value: %ld", value);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  if (value == (*(unsigned char *) var))
+    return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+  if (cdef->validator)
+  {
+    int rc = cdef->validator(cs, cdef, value, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+      return (rc | CSR_INV_VALIDATOR);
+  }
+
+  *(unsigned char *) var = value;
+  return CSR_SUCCESS;
+}
+
+/**
+ * enum_native_get - Get an int object from a Enumeration config item
+ * @param cs   Config items
+ * @param var  Variable to get
+ * @param cdef Variable definition
+ * @param err  Buffer for error messages
+ * @retval intptr_t Enumeration value
+ */
+static intptr_t enum_native_get(const struct ConfigSet *cs, void *var,
+                                const struct ConfigDef *cdef, struct Buffer *err)
+{
+  if (!cs || !var || !cdef)
+    return INT_MIN; /* LCOV_EXCL_LINE */
+
+  return *(unsigned char *) var;
+}
+
+/**
+ * enum_reset - Reset a Enumeration to its initial value
+ * @param cs   Config items
+ * @param var  Variable to reset
+ * @param cdef Variable definition
+ * @param err  Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+static int enum_reset(const struct ConfigSet *cs, void *var,
+                      const struct ConfigDef *cdef, struct Buffer *err)
+{
+  if (!cs || !var || !cdef)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  if (cdef->initial == (*(unsigned char *) var))
+    return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+  if (cdef->validator)
+  {
+    int rc = cdef->validator(cs, cdef, cdef->initial, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+      return (rc | CSR_INV_VALIDATOR);
+  }
+
+  *(unsigned char *) var = cdef->initial;
+  return CSR_SUCCESS;
+}
+
+/**
+ * enum_init - Register the Enumeration config type
+ * @param cs Config items
+ */
+void enum_init(struct ConfigSet *cs)
+{
+  const struct ConfigSetType cst_enum = {
+    "enum",
+    enum_string_set,
+    enum_string_get,
+    enum_native_set,
+    enum_native_get,
+    enum_reset,
+    NULL,
+  };
+  cs_register_type(cs, DT_ENUM, &cst_enum);
+}
diff --git a/config/enum.h b/config/enum.h
new file mode 100644 (file)
index 0000000..2467ebd
--- /dev/null
@@ -0,0 +1,40 @@
+/**
+ * @file
+ * Type representing an enumeration
+ *
+ * @authors
+ * Copyright (C) 2018 Richard Russon <rich@flatcap.org>
+ *
+ * @copyright
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MUTT_CONFIG_ENUM_H
+#define MUTT_CONFIG_ENUM_H
+
+struct ConfigSet;
+
+/**
+ * struct EnumDef - XXX
+ */
+struct EnumDef
+{
+  const char *name;
+  int count;
+  struct Mapping *lookup;
+};
+
+void enum_init(struct ConfigSet *cs);
+
+#endif /* MUTT_CONFIG_ENUM_H */
index 3d5604762b8277d1033b49dfb9f4ac938afdfb75..2dfe9fff0948e8e26e162bf44235c566c7e0b581 100644 (file)
@@ -30,6 +30,7 @@
  * | config/address.c    | @subpage config_address    |
  * | config/bool.c       | @subpage config_bool       |
  * | config/dump.c       | @subpage config_dump       |
+ * | config/enum.c       | @subpage config_enum       |
  * | config/long.c       | @subpage config_long       |
  * | config/magic.c      | @subpage config_magic      |
  * | config/mbtable.c    | @subpage config_mbtable    |
@@ -47,6 +48,7 @@
 #include "address.h"
 #include "bool.h"
 #include "dump.h"
+#include "enum.h"
 #include "inheritance.h"
 #include "long.h"
 #include "magic.h"
diff --git a/init.c b/init.c
index 206e8a3c644534da05c15b8b670efceba3ec438a..95eb8b5b155faaefc3634d65ae8f854f8e2f0a86 100644 (file)
--- a/init.c
+++ b/init.c
@@ -3815,6 +3815,7 @@ struct ConfigSet *init_config(size_t size)
 
   address_init(cs);
   bool_init(cs);
+  enum_init(cs);
   long_init(cs);
   magic_init(cs);
   mbtable_init(cs);
index d3b607d4c15ee23fa45400396ee062c9b214f4a9..40db22480c4c7cf3490b3ddb32fab1ccbcbdb357 100644 (file)
@@ -15,6 +15,7 @@ compress.c
 config/address.c
 config/bool.c
 config/dump.c
+config/enum.c
 config/long.c
 config/magic.c
 config/mbtable.c
index e84e047beb2e191d4e108b2923727c767be78554..661e11428a726c5e8daec3a5f3c6f47ec7796699 100644 (file)
@@ -86,6 +86,7 @@ CONFIG_OBJS   = test/config/account.o \
                  test/config/bool.o \
                  test/config/common.o \
                  test/config/dump.o \
+                 test/config/enum.o \
                  test/config/initial.o \
                  test/config/long.o \
                  test/config/magic.o \
diff --git a/test/config/enum.c b/test/config/enum.c
new file mode 100644 (file)
index 0000000..f0f9e1a
--- /dev/null
@@ -0,0 +1,684 @@
+/**
+ * @file
+ * Test code for the Enum object
+ *
+ * @authors
+ * Copyright (C) 2018 Richard Russon <rich@flatcap.org>
+ *
+ * @copyright
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#define TEST_NO_MAIN
+#include "acutest.h"
+#include "config.h"
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include "mutt/mutt.h"
+#include "config/common.h"
+#include "config/lib.h"
+#include "account.h"
+
+static unsigned char VarApple;
+static unsigned char VarBanana;
+static unsigned char VarCherry;
+static unsigned char VarDamson;
+static unsigned char VarElderberry;
+static unsigned char VarFig;
+static unsigned char VarGuava;
+static unsigned char VarHawthorn;
+static unsigned char VarIlama;
+static unsigned char VarJackfruit;
+static unsigned char VarKumquat;
+static unsigned char VarLemon;
+static unsigned char VarMango;
+static unsigned char VarNectarine;
+static unsigned char VarOlive;
+
+// clang-format off
+enum AnimalType
+{
+  ANIMAL_ANTELOPE  =  1,
+  ANIMAL_BADGER    =  2,
+  ANIMAL_CASSOWARY =  3,
+  ANIMAL_DINGO     = 40,
+  ANIMAL_ECHIDNA   = 41,
+  ANIMAL_FROG      = 42,
+};
+
+static struct Mapping AnimalMap[] = {
+  { "Antelope",  ANIMAL_ANTELOPE,  },
+  { "Badger",    ANIMAL_BADGER,    },
+  { "Cassowary", ANIMAL_CASSOWARY, },
+  { "Dingo",     ANIMAL_DINGO,     },
+  { "Echidna",   ANIMAL_ECHIDNA,   },
+  { "Frog",      ANIMAL_FROG,      },
+  // Alternatives
+  { "bird",      ANIMAL_CASSOWARY, },
+  { "amphibian", ANIMAL_FROG,      },
+  { "carnivore", ANIMAL_BADGER,    },
+  { "herbivore", ANIMAL_ANTELOPE,  },
+  { NULL,        0,                },
+};
+
+struct EnumDef AnimalDef = {
+  "animal",
+  5,
+  (struct Mapping *) &AnimalMap,
+};
+
+static struct ConfigDef Vars[] = {
+  { "Apple",      DT_ENUM, &VarApple,      ANIMAL_DINGO,    IP &AnimalDef, NULL              }, /* test_initial_values */
+  { "Banana",     DT_ENUM, &VarBanana,     ANIMAL_BADGER,   IP &AnimalDef, NULL              },
+  { "Cherry",     DT_ENUM, &VarCherry,     ANIMAL_FROG,     IP &AnimalDef, NULL              },
+  { "Damson",     DT_ENUM, &VarDamson,     ANIMAL_ANTELOPE, IP &AnimalDef, NULL              }, /* test_string_set */
+  { "Elderberry", DT_ENUM, &VarElderberry, ANIMAL_ANTELOPE, 0,             NULL              }, /* broken */
+  { "Fig",        DT_ENUM, &VarFig,        ANIMAL_ANTELOPE, IP &AnimalDef, NULL              }, /* test_string_get */
+  { "Guava",      DT_ENUM, &VarGuava,      ANIMAL_ANTELOPE, IP &AnimalDef, NULL              }, /* test_native_set */
+  { "Hawthorn",   DT_ENUM, &VarHawthorn,   ANIMAL_ANTELOPE, IP &AnimalDef, NULL              },
+  { "Ilama",      DT_ENUM, &VarIlama,      ANIMAL_ANTELOPE, IP &AnimalDef, NULL              }, /* test_native_get */
+  { "Jackfruit",  DT_ENUM, &VarJackfruit,  ANIMAL_ANTELOPE, IP &AnimalDef, NULL              }, /* test_reset */
+  { "Kumquat",    DT_ENUM, &VarKumquat,    ANIMAL_ANTELOPE, IP &AnimalDef, validator_fail    },
+  { "Lemon",      DT_ENUM, &VarLemon,      ANIMAL_ANTELOPE, IP &AnimalDef, validator_succeed }, /* test_validator */
+  { "Mango",      DT_ENUM, &VarMango,      ANIMAL_ANTELOPE, IP &AnimalDef, validator_warn    },
+  { "Nectarine",  DT_ENUM, &VarNectarine,  ANIMAL_ANTELOPE, IP &AnimalDef, validator_fail    },
+  { "Olive",      DT_ENUM, &VarOlive,      ANIMAL_ANTELOPE, IP &AnimalDef, NULL              }, /* test_inherit */
+  { NULL },
+};
+// clang-format on
+
+static bool test_initial_values(struct ConfigSet *cs, struct Buffer *err)
+{
+  log_line(__func__);
+  TEST_MSG("Apple = %d\n", VarApple);
+  TEST_MSG("Banana = %d\n", VarBanana);
+
+  if (!TEST_CHECK(VarApple == ANIMAL_DINGO))
+  {
+    TEST_MSG("Expected: %d\n", ANIMAL_DINGO);
+    TEST_MSG("Actual  : %d\n", VarApple);
+  }
+
+  if (!TEST_CHECK(VarBanana == ANIMAL_BADGER))
+  {
+    TEST_MSG("Expected: %d\n", ANIMAL_BADGER);
+    TEST_MSG("Actual  : %d\n", VarBanana);
+  }
+
+  cs_str_string_set(cs, "Apple", "Cassowary", err);
+  cs_str_string_set(cs, "Banana", "herbivore", err);
+
+  struct Buffer value;
+  mutt_buffer_init(&value);
+  value.dsize = 256;
+  value.data = mutt_mem_calloc(1, value.dsize);
+
+  int rc;
+
+  mutt_buffer_reset(&value);
+  rc = cs_str_initial_get(cs, "Apple", &value);
+  if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS))
+  {
+    TEST_MSG("%s\n", value.data);
+    FREE(&value.data);
+    return false;
+  }
+
+  if (!TEST_CHECK(mutt_str_strcmp(value.data, "Dingo") == 0))
+  {
+    TEST_MSG("Apple's initial value is wrong: '%s'\n", value.data);
+    FREE(&value.data);
+    return false;
+  }
+  TEST_MSG("Apple = %d\n", VarApple);
+  TEST_MSG("Apple's initial value is '%s'\n", value.data);
+
+  mutt_buffer_reset(&value);
+  rc = cs_str_initial_get(cs, "Banana", &value);
+  if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS))
+  {
+    TEST_MSG("%s\n", value.data);
+    FREE(&value.data);
+    return false;
+  }
+
+  if (!TEST_CHECK(mutt_str_strcmp(value.data, "Badger") == 0))
+  {
+    TEST_MSG("Banana's initial value is wrong: '%s'\n", value.data);
+    FREE(&value.data);
+    return false;
+  }
+  TEST_MSG("Banana = %d\n", VarBanana);
+  TEST_MSG("Banana's initial value is '%s'\n", NONULL(value.data));
+
+  mutt_buffer_reset(&value);
+  rc = cs_str_initial_set(cs, "Cherry", "bird", &value);
+  if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS))
+  {
+    TEST_MSG("%s\n", value.data);
+    FREE(&value.data);
+    return false;
+  }
+
+  mutt_buffer_reset(&value);
+  rc = cs_str_initial_get(cs, "Cherry", &value);
+  if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS))
+  {
+    TEST_MSG("%s\n", value.data);
+    FREE(&value.data);
+    return false;
+  }
+
+  TEST_MSG("Cherry = %d\n", VarCherry);
+  TEST_MSG("Cherry's initial value is %s\n", value.data);
+
+  FREE(&value.data);
+  log_line(__func__);
+  return true;
+}
+
+static bool test_string_set(struct ConfigSet *cs, struct Buffer *err)
+{
+  log_line(__func__);
+  const char *valid[] = { "Antelope", "ECHIDNA", "herbivore", "BIRD" };
+  int numbers[] = { 1, 41, 1, 3 };
+  const char *invalid[] = { "Frogs", "", NULL };
+  const char *name = "Damson";
+
+  int rc;
+  for (unsigned int i = 0; i < mutt_array_size(valid); i++)
+  {
+    VarDamson = ANIMAL_CASSOWARY;
+
+    TEST_MSG("Setting %s to %s\n", name, valid[i]);
+    mutt_buffer_reset(err);
+    rc = cs_str_string_set(cs, name, valid[i], err);
+    if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS))
+    {
+      TEST_MSG("%s\n", err->data);
+      return false;
+    }
+
+    if (rc & CSR_SUC_NO_CHANGE)
+    {
+      TEST_MSG("Value of %s wasn't changed\n", name);
+      continue;
+    }
+
+    if (!TEST_CHECK(VarDamson == numbers[i]))
+    {
+      TEST_MSG("Value of %s wasn't changed\n", name);
+      return false;
+    }
+    TEST_MSG("%s = %d, set by '%s'\n", name, VarDamson, valid[i]);
+    short_line();
+  }
+
+  for (unsigned int i = 0; i < mutt_array_size(invalid); i++)
+  {
+    TEST_MSG("Setting %s to %s\n", name, NONULL(invalid[i]));
+    mutt_buffer_reset(err);
+    rc = cs_str_string_set(cs, name, invalid[i], err);
+    if (!TEST_CHECK(CSR_RESULT(rc) != CSR_SUCCESS))
+    {
+      TEST_MSG("%s = %d, set by '%s'\n", name, VarDamson, invalid[i]);
+      TEST_MSG("This test should have failed\n");
+      return false;
+    }
+    else
+    {
+      TEST_MSG("Expected error: %s\n", err->data);
+    }
+    short_line();
+  }
+
+  name = "Elderberry";
+  const char *value2 = "Dingo";
+  short_line();
+  TEST_MSG("Setting %s to '%s'\n", name, value2);
+  rc = cs_str_string_set(cs, name, value2, err);
+  if (TEST_CHECK(CSR_RESULT(rc) != CSR_SUCCESS))
+  {
+    TEST_MSG("Expected error: %s\n", err->data);
+  }
+  else
+  {
+    TEST_MSG("This test should have failed\n");
+    return false;
+  }
+
+  log_line(__func__);
+  return true;
+}
+
+static bool test_string_get(struct ConfigSet *cs, struct Buffer *err)
+{
+  log_line(__func__);
+  const char *name = "Fig";
+
+  VarFig = ANIMAL_ECHIDNA;
+  mutt_buffer_reset(err);
+  int rc = cs_str_string_get(cs, name, err);
+  if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS))
+  {
+    TEST_MSG("Get failed: %s\n", err->data);
+    return false;
+  }
+  TEST_MSG("%s = %d, %s\n", name, VarFig, err->data);
+
+  VarFig = ANIMAL_DINGO;
+  mutt_buffer_reset(err);
+  rc = cs_str_string_get(cs, name, err);
+  if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS))
+  {
+    TEST_MSG("Get failed: %s\n", err->data);
+    return false;
+  }
+  TEST_MSG("%s = %d, %s\n", name, VarFig, err->data);
+
+  log_line(__func__);
+  return true;
+}
+
+static bool test_native_set(struct ConfigSet *cs, struct Buffer *err)
+{
+  log_line(__func__);
+  const char *name = "Guava";
+  unsigned char value = ANIMAL_CASSOWARY;
+
+  TEST_MSG("Setting %s to %d\n", name, value);
+  VarGuava = 0;
+  mutt_buffer_reset(err);
+  int rc = cs_str_native_set(cs, name, value, err);
+  if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS))
+  {
+    TEST_MSG("%s\n", err->data);
+    return false;
+  }
+
+  if (!TEST_CHECK(VarGuava == value))
+  {
+    TEST_MSG("Value of %s wasn't changed\n", name);
+    return false;
+  }
+
+  TEST_MSG("%s = %d, set to '%d'\n", name, VarGuava, value);
+
+  short_line();
+  TEST_MSG("Setting %s to %d\n", name, value);
+  rc = cs_str_native_set(cs, name, value, err);
+  if (!TEST_CHECK((rc & CSR_SUC_NO_CHANGE) != 0))
+  {
+    TEST_MSG("This test should have failed\n");
+    return false;
+  }
+  else
+  {
+    TEST_MSG("Value of %s wasn't changed\n", name);
+  }
+
+  name = "Hawthorn";
+  value = -42;
+  short_line();
+  TEST_MSG("Setting %s to %d\n", name, value);
+  rc = cs_str_native_set(cs, name, value, err);
+  if (TEST_CHECK(CSR_RESULT(rc) != CSR_SUCCESS))
+  {
+    TEST_MSG("Expected error: %s\n", err->data);
+  }
+  else
+  {
+    TEST_MSG("This test should have failed\n");
+    return false;
+  }
+
+  int invalid[] = { -1, 256 };
+  for (unsigned int i = 0; i < mutt_array_size(invalid); i++)
+  {
+    short_line();
+    VarGuava = ANIMAL_CASSOWARY;
+    TEST_MSG("Setting %s to %d\n", name, invalid[i]);
+    mutt_buffer_reset(err);
+    rc = cs_str_native_set(cs, name, invalid[i], err);
+    if (TEST_CHECK(CSR_RESULT(rc) != CSR_SUCCESS))
+    {
+      TEST_MSG("Expected error: %s\n", err->data);
+    }
+    else
+    {
+      TEST_MSG("%s = %d, set by '%d'\n", name, VarGuava, invalid[i]);
+      TEST_MSG("This test should have failed\n");
+      return false;
+    }
+  }
+
+  name = "Elderberry";
+  value = ANIMAL_ANTELOPE;
+  short_line();
+  TEST_MSG("Setting %s to %d\n", name, value);
+  rc = cs_str_native_set(cs, name, value, err);
+  if (TEST_CHECK(CSR_RESULT(rc) != CSR_SUCCESS))
+  {
+    TEST_MSG("Expected error: %s\n", err->data);
+  }
+  else
+  {
+    TEST_MSG("This test should have failed\n");
+    return false;
+  }
+
+  log_line(__func__);
+  return true;
+}
+
+static bool test_native_get(struct ConfigSet *cs, struct Buffer *err)
+{
+  log_line(__func__);
+  const char *name = "Ilama";
+
+  VarIlama = 253;
+  mutt_buffer_reset(err);
+  intptr_t value = cs_str_native_get(cs, name, err);
+  if (!TEST_CHECK(value != INT_MIN))
+  {
+    TEST_MSG("Get failed: %s\n", err->data);
+    return false;
+  }
+  TEST_MSG("%s = %ld\n", name, value);
+
+  log_line(__func__);
+  return true;
+}
+
+static bool test_reset(struct ConfigSet *cs, struct Buffer *err)
+{
+  log_line(__func__);
+  const char *name = "Jackfruit";
+  VarJackfruit = 253;
+  mutt_buffer_reset(err);
+
+  TEST_MSG("%s = %d\n", name, VarJackfruit);
+  int rc = cs_str_reset(cs, name, err);
+  if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS))
+  {
+    TEST_MSG("%s\n", err->data);
+    return false;
+  }
+
+  if (!TEST_CHECK(VarJackfruit != 253))
+  {
+    TEST_MSG("Value of %s wasn't changed\n", name);
+    return false;
+  }
+
+  TEST_MSG("Reset: %s = %d\n", name, VarJackfruit);
+
+  short_line();
+  name = "Kumquat";
+  mutt_buffer_reset(err);
+
+  TEST_MSG("Initial: %s = %d\n", name, VarKumquat);
+  dont_fail = true;
+  rc = cs_str_string_set(cs, name, "Dingo", err);
+  if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS))
+    return false;
+  TEST_MSG("Set: %s = %d\n", name, VarKumquat);
+  dont_fail = false;
+
+  rc = cs_str_reset(cs, name, err);
+  if (TEST_CHECK(CSR_RESULT(rc) != CSR_SUCCESS))
+  {
+    TEST_MSG("Expected error: %s\n", err->data);
+  }
+  else
+  {
+    TEST_MSG("%s\n", err->data);
+    return false;
+  }
+
+  if (!TEST_CHECK(VarKumquat == ANIMAL_DINGO))
+  {
+    TEST_MSG("Value of %s changed\n", name);
+    return false;
+  }
+
+  TEST_MSG("Reset: %s = %d\n", name, VarKumquat);
+
+  short_line();
+  name = "Jackfruit";
+  VarJackfruit = ANIMAL_ANTELOPE;
+  mutt_buffer_reset(err);
+
+  rc = cs_str_reset(cs, name, err);
+  if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS))
+  {
+    TEST_MSG("%s\n", err->data);
+    return false;
+  }
+
+  log_line(__func__);
+  return true;
+}
+
+static bool test_validator(struct ConfigSet *cs, struct Buffer *err)
+{
+  log_line(__func__);
+
+  const char *name = "Lemon";
+  VarLemon = ANIMAL_ANTELOPE;
+  mutt_buffer_reset(err);
+  int rc = cs_str_string_set(cs, name, "Dingo", err);
+  if (TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS))
+  {
+    TEST_MSG("%s\n", err->data);
+  }
+  else
+  {
+    TEST_MSG("%s\n", err->data);
+    return false;
+  }
+  TEST_MSG("String: %s = %d\n", name, VarLemon);
+  short_line();
+
+  VarLemon = 253;
+  mutt_buffer_reset(err);
+  rc = cs_str_native_set(cs, name, ANIMAL_ECHIDNA, err);
+  if (TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS))
+  {
+    TEST_MSG("%s\n", err->data);
+  }
+  else
+  {
+    TEST_MSG("%s\n", err->data);
+    return false;
+  }
+  TEST_MSG("Native: %s = %d\n", name, VarLemon);
+  short_line();
+
+  name = "Mango";
+  VarMango = 123;
+  mutt_buffer_reset(err);
+  rc = cs_str_string_set(cs, name, "bird", err);
+  if (TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS))
+  {
+    TEST_MSG("%s\n", err->data);
+  }
+  else
+  {
+    TEST_MSG("%s\n", err->data);
+    return false;
+  }
+  TEST_MSG("String: %s = %d\n", name, VarMango);
+  short_line();
+
+  VarMango = 253;
+  mutt_buffer_reset(err);
+  rc = cs_str_native_set(cs, name, ANIMAL_DINGO, err);
+  if (TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS))
+  {
+    TEST_MSG("%s\n", err->data);
+  }
+  else
+  {
+    TEST_MSG("%s\n", err->data);
+    return false;
+  }
+  TEST_MSG("Native: %s = %d\n", name, VarMango);
+  short_line();
+
+  name = "Nectarine";
+  VarNectarine = 123;
+  mutt_buffer_reset(err);
+  rc = cs_str_string_set(cs, name, "Cassowary", err);
+  if (TEST_CHECK(CSR_RESULT(rc) != CSR_SUCCESS))
+  {
+    TEST_MSG("Expected error: %s\n", err->data);
+  }
+  else
+  {
+    TEST_MSG("%s\n", err->data);
+    return false;
+  }
+  TEST_MSG("String: %s = %d\n", name, VarNectarine);
+  short_line();
+
+  VarNectarine = 253;
+  mutt_buffer_reset(err);
+  rc = cs_str_native_set(cs, name, ANIMAL_CASSOWARY, err);
+  if (TEST_CHECK(CSR_RESULT(rc) != CSR_SUCCESS))
+  {
+    TEST_MSG("Expected error: %s\n", err->data);
+  }
+  else
+  {
+    TEST_MSG("%s\n", err->data);
+    return false;
+  }
+  TEST_MSG("Native: %s = %d\n", name, VarNectarine);
+
+  log_line(__func__);
+  return true;
+}
+
+static void dump_native(struct ConfigSet *cs, const char *parent, const char *child)
+{
+  intptr_t pval = cs_str_native_get(cs, parent, NULL);
+  intptr_t cval = cs_str_native_get(cs, child, NULL);
+
+  TEST_MSG("%15s = %ld\n", parent, pval);
+  TEST_MSG("%15s = %ld\n", child, cval);
+}
+
+static bool test_inherit(struct ConfigSet *cs, struct Buffer *err)
+{
+  log_line(__func__);
+  bool result = false;
+
+  const char *account = "fruit";
+  const char *parent = "Olive";
+  char child[128];
+  snprintf(child, sizeof(child), "%s:%s", account, parent);
+
+  const char *AccountVarStr[] = {
+    parent,
+    NULL,
+  };
+
+  struct Account *a = account_new();
+  account_add_config(a, cs, account, AccountVarStr);
+
+  // set parent
+  VarOlive = ANIMAL_BADGER;
+  mutt_buffer_reset(err);
+  int rc = cs_str_string_set(cs, parent, "Dingo", err);
+  if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS))
+  {
+    TEST_MSG("Error: %s\n", err->data);
+    goto ti_out;
+  }
+  dump_native(cs, parent, child);
+  short_line();
+
+  // set child
+  mutt_buffer_reset(err);
+  rc = cs_str_string_set(cs, child, "Cassowary", err);
+  if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS))
+  {
+    TEST_MSG("Error: %s\n", err->data);
+    goto ti_out;
+  }
+  dump_native(cs, parent, child);
+  short_line();
+
+  // reset child
+  mutt_buffer_reset(err);
+  rc = cs_str_reset(cs, child, err);
+  if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS))
+  {
+    TEST_MSG("Error: %s\n", err->data);
+    goto ti_out;
+  }
+  dump_native(cs, parent, child);
+  short_line();
+
+  // reset parent
+  mutt_buffer_reset(err);
+  rc = cs_str_reset(cs, parent, err);
+  if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS))
+  {
+    TEST_MSG("Error: %s\n", err->data);
+    goto ti_out;
+  }
+  dump_native(cs, parent, child);
+
+  log_line(__func__);
+  result = true;
+ti_out:
+  account_free(&a);
+  return result;
+}
+
+void config_enum(void)
+{
+  struct Buffer err;
+  mutt_buffer_init(&err);
+  err.data = mutt_mem_calloc(1, 256);
+  err.dsize = 256;
+  mutt_buffer_reset(&err);
+
+  struct ConfigSet *cs = cs_new(30);
+
+  enum_init(cs);
+  if (!cs_register_variables(cs, Vars, 0))
+    return;
+
+  notify_observer_add(cs->notify, NT_CONFIG, 0, log_observer, 0);
+
+  set_list(cs);
+
+  TEST_CHECK(test_initial_values(cs, &err));
+  TEST_CHECK(test_string_set(cs, &err));
+  TEST_CHECK(test_string_get(cs, &err));
+  TEST_CHECK(test_native_set(cs, &err));
+  TEST_CHECK(test_native_get(cs, &err));
+  TEST_CHECK(test_reset(cs, &err));
+  TEST_CHECK(test_validator(cs, &err));
+  TEST_CHECK(test_inherit(cs, &err));
+
+  cs_free(&cs);
+  FREE(&err.data);
+  log_line(__func__);
+}
diff --git a/test/config/enum.h b/test/config/enum.h
new file mode 100644 (file)
index 0000000..8499783
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * @file
+ * Test code for the Enum object
+ *
+ * @authors
+ * Copyright (C) 2018 Richard Russon <rich@flatcap.org>
+ *
+ * @copyright
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TEST_ENUM_H
+#define _TEST_ENUM_H
+
+#include <stdbool.h>
+
+void config_enum(void);
+
+#endif /* _TEST_ENUM_H */
index afdc6e3f701e49c03b2b6ace9504d02e633c8778..669c6416ad666224e0d370a18b70af3bbeb19608 100644 (file)
   NEOMUTT_TEST_ITEM(config_synonym)                                            \
   NEOMUTT_TEST_ITEM(config_address)                                            \
   NEOMUTT_TEST_ITEM(config_bool)                                               \
+  NEOMUTT_TEST_ITEM(config_enum)                                               \
   NEOMUTT_TEST_ITEM(config_long)                                               \
   NEOMUTT_TEST_ITEM(config_magic)                                              \
   NEOMUTT_TEST_ITEM(config_mbtable)                                            \