[PATCH v3 00/23] log: Add commands for manipulating filters

classic Classic list List threaded Threaded
33 messages Options
12
Reply | Threaded
Open this post in threaded view
|

[PATCH v3 00/23] log: Add commands for manipulating filters

Sean Anderson
This series adds several commands for adding, listing, and removing log filters.
It also adds getopt, since the filter-add command needs to have several
optional arguments to be complete, and positional specification of those
arguments would have been difficult.

Changes in v3:
- Convert log_test from python to C
- Document assumption that erroneous results from log_get_cat_name begin with
  '<'
- Fix heading level of Filters section
- Remove a few more already-implemented features from the TODO list
- Update copyright for log_filter.c

Changes in v2:
- Add % before constants in kerneldocs
- Add a few informational commands
- Add compiletime assert on size of log_cat_name
- Add const qualifier to log_*_name
- Add option to remove all filters to filter-remove
- Clarify filter-* help text
- Clarify wording of filter documentation
- Converted log filter-* tests to C from python
- Document log_level_t and log_category_t members
- Expand documentation of getopt() to include examples
- Expose log_has_cat and log_has_file for filter tests
- Include enum definitions instead of re-documenting them
- Print an error message if the log level is invalid.
- Remove opt prefix from getopt_state members
- Reorganize log documentation; related sections should now be more proximate

Sean Anderson (23):
  log: Fix missing negation of ENOMEM
  log: Fix incorrect documentation of log_filter.cat_list
  log: Add additional const qualifier to arrays
  log: Add new category names to log_cat_name
  log: Use CONFIG_IS_ENABLED() for LOG_TEST
  log: Expose some helper functions
  log: Add function to create a filter with flags
  log: Add filter flag to deny on match
  test: log: Convert log_test from python to C
  test: log: Give tests names instead of numbers
  test: Add tests for LOGFF_DENY
  log: Add filter flag to match greater than a log level
  test: Add test for LOGFF_MIN
  cmd: log: Use sub-commands for log
  cmd: log: Split off log level parsing
  cmd: log: Add commands to list categories and drivers
  cmd: log: Make "log level" print all log levels
  lib: Add getopt
  test: Add a test for getopt
  cmd: log: Add commands to manipulate filters
  test: Add a test for log filter-*
  doc: Add log kerneldocs to documentation
  doc: Update logging documentation

 MAINTAINERS               |   1 +
 cmd/Kconfig               |   1 +
 cmd/log.c                 | 353 ++++++++++++++++++++++---
 common/log.c              |  66 ++---
 doc/api/getopt.rst        |   8 +
 doc/api/index.rst         |   1 +
 doc/develop/logging.rst   | 245 +++++++++--------
 include/getopt.h          | 130 +++++++++
 include/log.h             | 212 +++++++++++----
 include/test/log.h        |   3 +
 lib/Kconfig               |   5 +
 lib/Makefile              |   1 +
 lib/getopt.c              | 125 +++++++++
 test/lib/Makefile         |   1 +
 test/lib/getopt.c         | 123 +++++++++
 test/log/Makefile         |   1 +
 test/log/log_filter.c     | 108 ++++++++
 test/log/log_test.c       | 536 +++++++++++++++++++++++++-------------
 test/log/syslog_test.h    |   2 -
 test/py/tests/test_log.py | 104 --------
 20 files changed, 1482 insertions(+), 544 deletions(-)
 create mode 100644 doc/api/getopt.rst
 create mode 100644 include/getopt.h
 create mode 100644 lib/getopt.c
 create mode 100644 test/lib/getopt.c
 create mode 100644 test/log/log_filter.c

--
2.28.0

Reply | Threaded
Open this post in threaded view
|

[PATCH v3 01/23] log: Fix missing negation of ENOMEM

Sean Anderson
Errors returned should be negative.

Fixes: 45fac9fc18 ("log: Correct missing free() on error in log_add_filter()")

Signed-off-by: Sean Anderson <[hidden email]>
Reviewed-by: Heinrich Schuchardt <[hidden email]>
---

(no changes since v1)

 common/log.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/common/log.c b/common/log.c
index 1b10f6f180..807babbb6e 100644
--- a/common/log.c
+++ b/common/log.c
@@ -273,7 +273,7 @@ int log_add_filter(const char *drv_name, enum log_category_t cat_list[],
  if (file_list) {
  filt->file_list = strdup(file_list);
  if (!filt->file_list) {
- ret = ENOMEM;
+ ret = -ENOMEM;
  goto err;
  }
  }
--
2.28.0

Reply | Threaded
Open this post in threaded view
|

[PATCH v3 02/23] log: Fix incorrect documentation of log_filter.cat_list

Sean Anderson
In reply to this post by Sean Anderson
Logging category lists are terminated by LOGC_END, not LOGC_NONE.

Fixes: e9c8d49d54 ("log: Add an implementation of logging")

Signed-off-by: Sean Anderson <[hidden email]>
Reviewed-by: Heinrich Schuchardt <[hidden email]>
---

(no changes since v2)

Changes in v2:
- Also fix misdocumentation of for log_add_filter()

 include/log.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/include/log.h b/include/log.h
index 4acc087b2e..3487c936c9 100644
--- a/include/log.h
+++ b/include/log.h
@@ -368,7 +368,7 @@ enum log_filter_flags {
  * new filter, and must be provided when removing a previously added
  * filter.
  * @flags: Flags for this filter (LOGFF_...)
- * @cat_list: List of categories to allow (terminated by LOGC_none). If empty
+ * @cat_list: List of categories to allow (terminated by %LOGC_END). If empty
  * then all categories are permitted. Up to LOGF_MAX_CATEGORIES entries
  * can be provided
  * @max_level: Maximum log level to allow
@@ -446,7 +446,7 @@ int do_log_test(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
  *
  * @drv_name: Driver name to add the filter to (since each driver only has a
  * single device)
- * @cat_list: List of categories to allow (terminated by LOGC_none). If empty
+ * @cat_list: List of categories to allow (terminated by %LOGC_END). If empty
  * then all categories are permitted. Up to LOGF_MAX_CATEGORIES entries
  * can be provided
  * @max_level: Maximum log level to allow
--
2.28.0

Reply | Threaded
Open this post in threaded view
|

[PATCH v3 03/23] log: Add additional const qualifier to arrays

Sean Anderson
In reply to this post by Sean Anderson
Both these arrays and their members are const. Fixes checkpatch complaint.

Signed-off-by: Sean Anderson <[hidden email]>
Reviewed-by: Simon Glass <[hidden email]>
---

(no changes since v2)

Changes in v2:
- New

 common/log.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/common/log.c b/common/log.c
index 807babbb6e..f3d9f4a728 100644
--- a/common/log.c
+++ b/common/log.c
@@ -13,7 +13,7 @@
 
 DECLARE_GLOBAL_DATA_PTR;
 
-static const char *log_cat_name[LOGC_COUNT - LOGC_NONE] = {
+static const char *const log_cat_name[LOGC_COUNT - LOGC_NONE] = {
  "none",
  "arch",
  "board",
@@ -23,7 +23,7 @@ static const char *log_cat_name[LOGC_COUNT - LOGC_NONE] = {
  "efi",
 };
 
-static const char *log_level_name[LOGL_COUNT] = {
+static const char *const log_level_name[LOGL_COUNT] = {
  "EMERG",
  "ALERT",
  "CRIT",
--
2.28.0

Reply | Threaded
Open this post in threaded view
|

[PATCH v3 04/23] log: Add new category names to log_cat_name

Sean Anderson
In reply to this post by Sean Anderson
Without every category between LOGC_NONE and LOGC_COUNT present in
log_cat_name, log_get_cat_by_name will dereference NULL pointers if it
doesn't find a name early enough.

Fixes: c3aed5db59 ("sandbox: spi: Add more logging")
Fixes: a5c13b68e7 ("sandbox: log: Add a category for sandbox")
Fixes: 9f407d4ef0 ("Add core support for a bloblist to convey data from SPL")
Fixes: cce61fc428 ("dm: devres: Convert to use logging")
Fixes: 7ca2850cbc ("dm: core: Add basic ACPI support")

Signed-off-by: Sean Anderson <[hidden email]>
Reviewed-by: Simon Glass <[hidden email]>
---

(no changes since v2)

Changes in v2:
- Add compiletime assert on size of log_cat_name

 common/log.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/common/log.c b/common/log.c
index f3d9f4a728..5a588c4152 100644
--- a/common/log.c
+++ b/common/log.c
@@ -13,7 +13,7 @@
 
 DECLARE_GLOBAL_DATA_PTR;
 
-static const char *const log_cat_name[LOGC_COUNT - LOGC_NONE] = {
+static const char *const log_cat_name[] = {
  "none",
  "arch",
  "board",
@@ -21,6 +21,11 @@ static const char *const log_cat_name[LOGC_COUNT - LOGC_NONE] = {
  "driver-model",
  "device-tree",
  "efi",
+ "alloc",
+ "sandbox",
+ "bloblist",
+ "devres",
+ "acpi",
 };
 
 static const char *const log_level_name[LOGL_COUNT] = {
@@ -40,6 +45,9 @@ const char *log_get_cat_name(enum log_category_t cat)
 {
  const char *name;
 
+ compiletime_assert(ARRAY_SIZE(log_cat_name) == LOGC_COUNT - LOGC_NONE,
+   "missing logging category name");
+
  if (cat < 0 || cat >= LOGC_COUNT)
  return "<invalid>";
  if (cat >= LOGC_NONE)
--
2.28.0

Reply | Threaded
Open this post in threaded view
|

[PATCH v3 05/23] log: Use CONFIG_IS_ENABLED() for LOG_TEST

Sean Anderson
In reply to this post by Sean Anderson
Checkpatch complains about using #ifdef for CONFIG variables.

Signed-off-by: Sean Anderson <[hidden email]>
Reviewed-by: Simon Glass <[hidden email]>
---

(no changes since v1)

 cmd/log.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/cmd/log.c b/cmd/log.c
index 6afe6ead25..16a6ef7539 100644
--- a/cmd/log.c
+++ b/cmd/log.c
@@ -105,7 +105,7 @@ static int do_log_rec(struct cmd_tbl *cmdtp, int flag, int argc,
 
 static struct cmd_tbl log_sub[] = {
  U_BOOT_CMD_MKENT(level, CONFIG_SYS_MAXARGS, 1, do_log_level, "", ""),
-#ifdef CONFIG_LOG_TEST
+#if CONFIG_IS_ENABLED(LOG_TEST)
  U_BOOT_CMD_MKENT(test, 2, 1, do_log_test, "", ""),
 #endif
  U_BOOT_CMD_MKENT(format, CONFIG_SYS_MAXARGS, 1, do_log_format, "", ""),
@@ -133,7 +133,7 @@ static int do_log(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
 #ifdef CONFIG_SYS_LONGHELP
 static char log_help_text[] =
  "level - get/set log level\n"
-#ifdef CONFIG_LOG_TEST
+#if CONFIG_IS_ENABLED(LOG_TEST)
  "log test - run log tests\n"
 #endif
  "log format <fmt> - set log output format. <fmt> is a string where\n"
--
2.28.0

Reply | Threaded
Open this post in threaded view
|

[PATCH v3 06/23] log: Expose some helper functions

Sean Anderson
In reply to this post by Sean Anderson
These functions are required by "cmd: log: Add commands to manipulate
filters" and "test: Add a test for log filter-*".

Signed-off-by: Sean Anderson <[hidden email]>
Reviewed-by: Simon Glass <[hidden email]>
---

(no changes since v2)

Changes in v2:
- Expose log_has_cat and log_has_file for filter tests

 common/log.c  | 23 +++--------------------
 include/log.h | 31 +++++++++++++++++++++++++++++++
 2 files changed, 34 insertions(+), 20 deletions(-)

diff --git a/common/log.c b/common/log.c
index 5a588c4152..63e8df820e 100644
--- a/common/log.c
+++ b/common/log.c
@@ -96,7 +96,7 @@ enum log_level_t log_get_level_by_name(const char *name)
  return LOGL_NONE;
 }
 
-static struct log_device *log_device_find_by_name(const char *drv_name)
+struct log_device *log_device_find_by_name(const char *drv_name)
 {
  struct log_device *ldev;
 
@@ -108,15 +108,7 @@ static struct log_device *log_device_find_by_name(const char *drv_name)
  return NULL;
 }
 
-/**
- * log_has_cat() - check if a log category exists within a list
- *
- * @cat_list: List of categories to check, at most LOGF_MAX_CATEGORIES entries
- * long, terminated by LC_END if fewer
- * @cat: Category to search for
- * @return true if @cat is in @cat_list, else false
- */
-static bool log_has_cat(enum log_category_t cat_list[], enum log_category_t cat)
+bool log_has_cat(enum log_category_t cat_list[], enum log_category_t cat)
 {
  int i;
 
@@ -128,16 +120,7 @@ static bool log_has_cat(enum log_category_t cat_list[], enum log_category_t cat)
  return false;
 }
 
-/**
- * log_has_file() - check if a file is with a list
- *
- * @file_list: List of files to check, separated by comma
- * @file: File to check for. This string is matched against the end of each
- * file in the list, i.e. ignoring any preceding path. The list is
- * intended to consist of relative pathnames, e.g. common/main.c,cmd/log.c
- * @return true if @file is in @file_list, else false
- */
-static bool log_has_file(const char *file_list, const char *file)
+bool log_has_file(const char *file_list, const char *file)
 {
  int file_len = strlen(file);
  const char *s, *p;
diff --git a/include/log.h b/include/log.h
index 3487c936c9..8d0227a384 100644
--- a/include/log.h
+++ b/include/log.h
@@ -425,6 +425,37 @@ const char *log_get_level_name(enum log_level_t level);
  */
 enum log_level_t log_get_level_by_name(const char *name);
 
+/**
+ * log_device_find_by_name() - Look up a log device by its driver's name
+ *
+ * @drv_name: Name of the driver
+ * @return the log device, or NULL if not found
+ */
+struct log_device *log_device_find_by_name(const char *drv_name);
+
+/**
+ * log_has_cat() - check if a log category exists within a list
+ *
+ * @cat_list: List of categories to check, at most %LOGF_MAX_CATEGORIES entries
+ * long, terminated by %LC_END if fewer
+ * @cat: Category to search for
+ *
+ * Return: ``true`` if @cat is in @cat_list, else ``false``
+ */
+bool log_has_cat(enum log_category_t cat_list[], enum log_category_t cat);
+
+/**
+ * log_has_file() - check if a file is with a list
+ *
+ * @file_list: List of files to check, separated by comma
+ * @file: File to check for. This string is matched against the end of each
+ * file in the list, i.e. ignoring any preceding path. The list is
+ * intended to consist of relative pathnames, e.g. common/main.c,cmd/log.c
+ *
+ * Return: ``true`` if @file is in @file_list, else ``false``
+ */
+bool log_has_file(const char *file_list, const char *file);
+
 /* Log format flags (bit numbers) for gd->log_fmt. See log_fmt_chars */
 enum log_fmt {
  LOGF_CAT = 0,
--
2.28.0

Reply | Threaded
Open this post in threaded view
|

[PATCH v3 07/23] log: Add function to create a filter with flags

Sean Anderson
In reply to this post by Sean Anderson
This function exposes a way to specify flags when creating a filter.

Signed-off-by: Sean Anderson <[hidden email]>
Reviewed-by: Simon Glass <[hidden email]>
---

(no changes since v1)

 common/log.c  |  6 ++++--
 include/log.h | 29 +++++++++++++++++++++++++++--
 2 files changed, 31 insertions(+), 4 deletions(-)

diff --git a/common/log.c b/common/log.c
index 63e8df820e..d43c9eb15d 100644
--- a/common/log.c
+++ b/common/log.c
@@ -233,8 +233,9 @@ int _log(enum log_category_t cat, enum log_level_t level, const char *file,
  return 0;
 }
 
-int log_add_filter(const char *drv_name, enum log_category_t cat_list[],
-   enum log_level_t max_level, const char *file_list)
+int log_add_filter_flags(const char *drv_name, enum log_category_t cat_list[],
+ enum log_level_t max_level, const char *file_list,
+ int flags)
 {
  struct log_filter *filt;
  struct log_device *ldev;
@@ -248,6 +249,7 @@ int log_add_filter(const char *drv_name, enum log_category_t cat_list[],
  if (!filt)
  return -ENOMEM;
 
+ filt->flags = flags;
  if (cat_list) {
  filt->flags |= LOGFF_HAS_CAT;
  for (i = 0; ; i++) {
diff --git a/include/log.h b/include/log.h
index 8d0227a384..4b33815f01 100644
--- a/include/log.h
+++ b/include/log.h
@@ -472,6 +472,25 @@ enum log_fmt {
 /* Handle the 'log test' command */
 int do_log_test(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
 
+/**
+ * log_add_filter_flags() - Add a new filter to a log device, specifying flags
+ *
+ * @drv_name: Driver name to add the filter to (since each driver only has a
+ * single device)
+ * @flags: Flags for this filter (LOGFF_...)
+ * @cat_list: List of categories to allow (terminated by %LOGC_END). If empty
+ * then all categories are permitted. Up to LOGF_MAX_CATEGORIES entries
+ * can be provided
+ * @max_level: Maximum log level to allow
+ * @file_list: List of files to allow, separated by comma. If NULL then all
+ * files are permitted
+ * @return the sequence number of the new filter (>=0) if the filter was added,
+ * or a -ve value on error
+ */
+int log_add_filter_flags(const char *drv_name, enum log_category_t cat_list[],
+ enum log_level_t max_level, const char *file_list,
+ int flags);
+
 /**
  * log_add_filter() - Add a new filter to a log device
  *
@@ -486,8 +505,14 @@ int do_log_test(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
  * @return the sequence number of the new filter (>=0) if the filter was added,
  * or a -ve value on error
  */
-int log_add_filter(const char *drv_name, enum log_category_t cat_list[],
-   enum log_level_t max_level, const char *file_list);
+static inline int log_add_filter(const char *drv_name,
+ enum log_category_t cat_list[],
+ enum log_level_t max_level,
+ const char *file_list)
+{
+ return log_add_filter_flags(drv_name, cat_list, max_level, file_list,
+    0);
+}
 
 /**
  * log_remove_filter() - Remove a filter from a log device
--
2.28.0

Reply | Threaded
Open this post in threaded view
|

[PATCH v3 08/23] log: Add filter flag to deny on match

Sean Anderson
In reply to this post by Sean Anderson
Without this flag, log filters can only explicitly accept messages.
Allowing denial makes it easier to filter certain subsystems. Unlike
allow-ing filters, deny-ing filters are added to the beginning of the
filter list. This should do the Right Thing most of the time, but it's
less-universal than allowing filters to be inserted anywhere. If this
becomes a problem, then perhaps log_filter_add* should take a filter number
to insert before/after.

Signed-off-by: Sean Anderson <[hidden email]>
Reviewed-by: Simon Glass <[hidden email]>
---

(no changes since v1)

 common/log.c  | 12 ++++++++++--
 include/log.h | 11 ++++++++++-
 2 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/common/log.c b/common/log.c
index d43c9eb15d..878800b3bb 100644
--- a/common/log.c
+++ b/common/log.c
@@ -167,7 +167,11 @@ static bool log_passes_filters(struct log_device *ldev, struct log_rec *rec)
  if (filt->file_list &&
     !log_has_file(filt->file_list, rec->file))
  continue;
- return true;
+
+ if (filt->flags & LOGFF_DENY)
+ return false;
+ else
+ return true;
  }
 
  return false;
@@ -271,7 +275,11 @@ int log_add_filter_flags(const char *drv_name, enum log_category_t cat_list[],
  }
  }
  filt->filter_num = ldev->next_filter_num++;
- list_add_tail(&filt->sibling_node, &ldev->filter_head);
+ /* Add deny filters to the beginning of the list */
+ if (flags & LOGFF_DENY)
+ list_add(&filt->sibling_node, &ldev->filter_head);
+ else
+ list_add_tail(&filt->sibling_node, &ldev->filter_head);
 
  return filt->filter_num;
 
diff --git a/include/log.h b/include/log.h
index 4b33815f01..8cabe82eb5 100644
--- a/include/log.h
+++ b/include/log.h
@@ -357,13 +357,22 @@ enum {
  LOGF_MAX_CATEGORIES = 5, /* maximum categories per filter */
 };
 
+/**
+ * enum log_filter_flags - Flags which modify a filter
+ */
 enum log_filter_flags {
- LOGFF_HAS_CAT = 1 << 0, /* Filter has a category list */
+ /** @LOGFF_HAS_CAT: Filter has a category list */
+ LOGFF_HAS_CAT = 1 << 0,
+ /** @LOGFF_DENY: Filter denies matching messages */
+ LOGFF_DENY = 1 << 1,
 };
 
 /**
  * struct log_filter - criterial to filter out log messages
  *
+ * If a message matches all criteria, then it is allowed. If LOGFF_DENY is set,
+ * then it is denied instead.
+ *
  * @filter_num: Sequence number of this filter.  This is returned when adding a
  * new filter, and must be provided when removing a previously added
  * filter.
--
2.28.0

Reply | Threaded
Open this post in threaded view
|

[PATCH v3 09/23] test: log: Convert log_test from python to C

Sean Anderson
In reply to this post by Sean Anderson
When rebasing this series I had to renumber all my log tests because
someone made another log test in the meantime. This involved updaing a
number in several places (C and python), and it wasn't checked by the
compiler. So I though "how hard could it be to just rewrite in C?" And
though it wasn't hard, it *was* tedious. Tests are numbered the same as
before to allow for easier review.

A note that if a test fails, everything after it will probably also fail.
This is because that test won't clean up its filters.  There's no easy way
to do the cleanup, except perhaps removing all filters in a wrapper
function.

Signed-off-by: Sean Anderson <[hidden email]>
---

Changes in v3:
- New

 cmd/log.c                 |   6 -
 include/test/log.h        |   2 +
 test/log/log_test.c       | 446 ++++++++++++++++++++++----------------
 test/log/syslog_test.h    |   2 -
 test/py/tests/test_log.py | 104 ---------
 5 files changed, 260 insertions(+), 300 deletions(-)

diff --git a/cmd/log.c b/cmd/log.c
index 16a6ef7539..d20bfdf744 100644
--- a/cmd/log.c
+++ b/cmd/log.c
@@ -105,9 +105,6 @@ static int do_log_rec(struct cmd_tbl *cmdtp, int flag, int argc,
 
 static struct cmd_tbl log_sub[] = {
  U_BOOT_CMD_MKENT(level, CONFIG_SYS_MAXARGS, 1, do_log_level, "", ""),
-#if CONFIG_IS_ENABLED(LOG_TEST)
- U_BOOT_CMD_MKENT(test, 2, 1, do_log_test, "", ""),
-#endif
  U_BOOT_CMD_MKENT(format, CONFIG_SYS_MAXARGS, 1, do_log_format, "", ""),
  U_BOOT_CMD_MKENT(rec, CONFIG_SYS_MAXARGS, 1, do_log_rec, "", ""),
 };
@@ -133,9 +130,6 @@ static int do_log(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
 #ifdef CONFIG_SYS_LONGHELP
 static char log_help_text[] =
  "level - get/set log level\n"
-#if CONFIG_IS_ENABLED(LOG_TEST)
- "log test - run log tests\n"
-#endif
  "log format <fmt> - set log output format. <fmt> is a string where\n"
  "\teach letter indicates something that should be displayed:\n"
  "\tc=category, l=level, F=file, L=line number, f=function, m=msg\n"
diff --git a/include/test/log.h b/include/test/log.h
index c661cde75a..772e197806 100644
--- a/include/test/log.h
+++ b/include/test/log.h
@@ -10,6 +10,8 @@
 
 #include <test/test.h>
 
+#define LOGF_TEST (BIT(LOGF_FUNC) | BIT(LOGF_MSG))
+
 /* Declare a new logging test */
 #define LOG_TEST(_name) UNIT_TEST(_name, 0, log_test)
 
diff --git a/test/log/log_test.c b/test/log/log_test.c
index 6a60ff6be3..c5ca6dcb7a 100644
--- a/test/log/log_test.c
+++ b/test/log/log_test.c
@@ -9,12 +9,17 @@
 #include <common.h>
 #include <command.h>
 #include <log.h>
+#include <test/log.h>
+#include <test/ut.h>
+
+DECLARE_GLOBAL_DATA_PTR;
 
 /* emit some sample log records in different ways, for testing */
-static int log_run(enum uclass_id cat, const char *file)
+static int do_log_run(int cat, const char *file)
 {
  int i;
 
+ gd->log_fmt = LOGF_TEST;
  debug("debug\n");
  for (i = LOGL_FIRST; i < LOGL_COUNT; i++) {
  log(cat, i, "log %d\n", i);
@@ -22,203 +27,268 @@ static int log_run(enum uclass_id cat, const char *file)
      i);
  }
 
+ gd->log_fmt = log_get_default_format();
  return 0;
 }
 
-static int log_test(int testnum)
+#define log_run_cat(cat) do_log_run(cat, "file")
+#define log_run_file(file) do_log_run(UCLASS_SPI, file)
+#define log_run() do_log_run(UCLASS_SPI, "file")
+
+#define EXPECT_LOG BIT(0)
+#define EXPECT_DIRECT BIT(1)
+#define EXPECT_EXTRA BIT(2)
+
+static int do_check_log_entries(struct unit_test_state *uts, int flags, int min,
+ int max)
 {
- int ret;
+ int i;
 
- printf("test %d\n", testnum);
- switch (testnum) {
- case 0: {
- /* Check a category filter using the first category */
- enum log_category_t cat_list[] = {
- log_uc_cat(UCLASS_MMC), log_uc_cat(UCLASS_SPI),
- LOGC_NONE, LOGC_END
- };
-
- ret = log_add_filter("console", cat_list, LOGL_MAX, NULL);
- if (ret < 0)
- return ret;
- log_run(UCLASS_MMC, "file");
- ret = log_remove_filter("console", ret);
- if (ret < 0)
- return ret;
- break;
- }
- case 1: {
- /* Check a category filter using the second category */
- enum log_category_t cat_list[] = {
- log_uc_cat(UCLASS_MMC), log_uc_cat(UCLASS_SPI), LOGC_END
- };
-
- ret = log_add_filter("console", cat_list, LOGL_MAX, NULL);
- if (ret < 0)
- return ret;
- log_run(UCLASS_SPI, "file");
- ret = log_remove_filter("console", ret);
- if (ret < 0)
- return ret;
- break;
- }
- case 2: {
- /* Check a category filter that should block log entries */
- enum log_category_t cat_list[] = {
- log_uc_cat(UCLASS_MMC),  LOGC_NONE, LOGC_END
- };
-
- ret = log_add_filter("console", cat_list, LOGL_MAX, NULL);
- if (ret < 0)
- return ret;
- log_run(UCLASS_SPI, "file");
- ret = log_remove_filter("console", ret);
- if (ret < 0)
- return ret;
- break;
- }
- case 3: {
- /* Check a passing file filter */
- ret = log_add_filter("console", NULL, LOGL_MAX, "file");
- if (ret < 0)
- return ret;
- log_run(UCLASS_SPI, "file");
- ret = log_remove_filter("console", ret);
- if (ret < 0)
- return ret;
- break;
- }
- case 4: {
- /* Check a failing file filter */
- ret = log_add_filter("console", NULL, LOGL_MAX, "file");
- if (ret < 0)
- return ret;
- log_run(UCLASS_SPI, "file2");
- ret = log_remove_filter("console", ret);
- if (ret < 0)
- return ret;
- break;
- }
- case 5: {
- /* Check a passing file filter (second in list) */
- ret = log_add_filter("console", NULL, LOGL_MAX, "file,file2");
- if (ret < 0)
- return ret;
- log_run(UCLASS_SPI, "file2");
- ret = log_remove_filter("console", ret);
- if (ret < 0)
- return ret;
- break;
- }
- case 6: {
- /* Check a passing file filter */
- ret = log_add_filter("console", NULL, LOGL_MAX,
-     "file,file2,log/log_test.c");
- if (ret < 0)
- return ret;
- log_run(UCLASS_SPI, "file2");
- ret = log_remove_filter("console", ret);
- if (ret < 0)
- return ret;
- break;
- }
- case 7: {
- /* Check a log level filter */
- ret = log_add_filter("console", NULL, LOGL_WARNING, NULL);
- if (ret < 0)
- return ret;
- log_run(UCLASS_SPI, "file");
- ret = log_remove_filter("console", ret);
- if (ret < 0)
- return ret;
- break;
- }
- case 8: {
- /* Check two filters, one of which passes everything */
- int filt1, filt2;
-
- ret = log_add_filter("console", NULL, LOGL_WARNING, NULL);
- if (ret < 0)
- return ret;
- filt1 = ret;
- ret = log_add_filter("console", NULL, LOGL_MAX, NULL);
- if (ret < 0)
- return ret;
- filt2 = ret;
- log_run(UCLASS_SPI, "file");
- ret = log_remove_filter("console", filt1);
- if (ret < 0)
- return ret;
- ret = log_remove_filter("console", filt2);
- if (ret < 0)
- return ret;
- break;
- }
- case 9: {
- /* Check three filters, which together pass everything */
- int filt1, filt2, filt3;
-
- ret = log_add_filter("console", NULL, LOGL_MAX, "file)");
- if (ret < 0)
- return ret;
- filt1 = ret;
- ret = log_add_filter("console", NULL, LOGL_MAX, "file2");
- if (ret < 0)
- return ret;
- filt2 = ret;
- ret = log_add_filter("console", NULL, LOGL_MAX,
-     "log/log_test.c");
- if (ret < 0)
- return ret;
- filt3 = ret;
- log_run(UCLASS_SPI, "file2");
- ret = log_remove_filter("console", filt1);
- if (ret < 0)
- return ret;
- ret = log_remove_filter("console", filt2);
- if (ret < 0)
- return ret;
- ret = log_remove_filter("console", filt3);
- if (ret < 0)
- return ret;
- break;
- }
- case 10: {
- log_err("level %d\n", LOGL_EMERG);
- log_err("level %d\n", LOGL_ALERT);
- log_err("level %d\n", LOGL_CRIT);
- log_err("level %d\n", LOGL_ERR);
- log_warning("level %d\n", LOGL_WARNING);
- log_notice("level %d\n", LOGL_NOTICE);
- log_info("level %d\n", LOGL_INFO);
- log_debug("level %d\n", LOGL_DEBUG);
- log_content("level %d\n", LOGL_DEBUG_CONTENT);
- log_io("level %d\n", LOGL_DEBUG_IO);
- break;
- }
- case 11:
- log_err("default\n");
- ret = log_device_set_enable(LOG_GET_DRIVER(console), false);
- log_err("disabled\n");
- ret = log_device_set_enable(LOG_GET_DRIVER(console), true);
- log_err("enabled\n");
- break;
- }
+ for (i = min; i <= max; i++) {
+ if (flags & EXPECT_LOG)
+ ut_assert_nextline("do_log_run() log %d", i);
+ if (flags & EXPECT_DIRECT)
+ ut_assert_nextline("func() _log %d", i);
+ }
+ if (flags & EXPECT_EXTRA)
+ for (; i <= LOGL_MAX ; i++)
+ ut_assert_nextline("func() _log %d", i);
 
+ ut_assert_console_end();
  return 0;
 }
 
-int do_log_test(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
+#define check_log_entries_flags_levels(flags, min, max) do {\
+ int ret = do_check_log_entries(uts, flags, min, max); \
+ if (ret) \
+ return ret; \
+} while (0)
+
+#define check_log_entries_flags(flags) \
+ check_log_entries_flags_levels(flags, LOGL_FIRST, _LOG_MAX_LEVEL)
+#define check_log_entries() check_log_entries_flags(EXPECT_LOG | EXPECT_DIRECT)
+#define check_log_entries_extra() \
+ check_log_entries_flags(EXPECT_LOG | EXPECT_DIRECT | EXPECT_EXTRA)
+#define check_log_entries_none() check_log_entries_flags(0)
+
+/* Check a category filter using the first category */
+int log_test_00(struct unit_test_state *uts)
+{
+ enum log_category_t cat_list[] = {
+ log_uc_cat(UCLASS_MMC), log_uc_cat(UCLASS_SPI),
+ LOGC_NONE, LOGC_END
+ };
+ int filt;
+
+ filt = log_add_filter("console", cat_list, LOGL_MAX, NULL);
+ ut_assert(filt >= 0);
+
+ ut_assertok(console_record_reset_enable());
+ log_run_cat(UCLASS_MMC);
+ check_log_entries_extra();
+
+ ut_assertok(console_record_reset_enable());
+ log_run_cat(UCLASS_SPI);
+ check_log_entries_extra();
+
+ ut_assertok(log_remove_filter("console", filt));
+ return 0;
+}
+LOG_TEST_FLAGS(log_test_00, UT_TESTF_CONSOLE_REC);
+
+/* Check a category filter that should block log entries */
+int log_test_02(struct unit_test_state *uts)
+{
+ enum log_category_t cat_list[] = {
+ log_uc_cat(UCLASS_MMC),  LOGC_NONE, LOGC_END
+ };
+ int filt;
+
+ filt = log_add_filter("console", cat_list, LOGL_MAX, NULL);
+ ut_assert(filt >= 0);
+
+ ut_assertok(console_record_reset_enable());
+ log_run_cat(UCLASS_SPI);
+ check_log_entries_none();
+
+ ut_assertok(log_remove_filter("console", filt));
+ return 0;
+}
+LOG_TEST_FLAGS(log_test_02, UT_TESTF_CONSOLE_REC);
+
+/* Check passing and failing file filters */
+int log_test_03(struct unit_test_state *uts)
+{
+ int filt;
+
+ filt = log_add_filter("console", NULL, LOGL_MAX, "file");
+ ut_assert(filt >= 0);
+
+ ut_assertok(console_record_reset_enable());
+ log_run_file("file");
+ check_log_entries_flags(EXPECT_DIRECT | EXPECT_EXTRA);
+
+ ut_assertok(console_record_reset_enable());
+ log_run_file("file2");
+ check_log_entries_none();
+
+ ut_assertok(log_remove_filter("console", filt));
+ return 0;
+}
+LOG_TEST_FLAGS(log_test_03, UT_TESTF_CONSOLE_REC);
+
+/* Check a passing file filter (second in list) */
+int log_test_05(struct unit_test_state *uts)
+{
+ int filt;
+
+ filt = log_add_filter("console", NULL, LOGL_MAX, "file,file2");
+ ut_assert(filt >= 0);
+
+ ut_assertok(console_record_reset_enable());
+ log_run_file("file2");
+ check_log_entries_flags(EXPECT_DIRECT | EXPECT_EXTRA);
+
+ ut_assertok(log_remove_filter("console", filt));
+ return 0;
+}
+LOG_TEST_FLAGS(log_test_05, UT_TESTF_CONSOLE_REC);
+
+/* Check a passing file filter (middle of list) */
+int log_test_06(struct unit_test_state *uts)
+{
+ int filt;
+
+ filt = log_add_filter("console", NULL, LOGL_MAX,
+      "file,file2,log/log_test.c");
+ ut_assert(filt >= 0);
+
+ ut_assertok(console_record_reset_enable());
+ log_run_file("file2");
+ check_log_entries_extra();
+
+ ut_assertok(log_remove_filter("console", filt));
+ return 0;
+}
+LOG_TEST_FLAGS(log_test_06, UT_TESTF_CONSOLE_REC);
+
+/* Check a log level filter */
+int log_test_07(struct unit_test_state *uts)
+{
+ int filt;
+
+ filt = log_add_filter("console", NULL, LOGL_WARNING, NULL);
+ ut_assert(filt >= 0);
+
+ ut_assertok(console_record_reset_enable());
+ log_run();
+ check_log_entries_flags_levels(EXPECT_LOG | EXPECT_DIRECT, LOGL_FIRST,
+       LOGL_WARNING);
+
+ ut_assertok(log_remove_filter("console", filt));
+ return 0;
+}
+LOG_TEST_FLAGS(log_test_07, UT_TESTF_CONSOLE_REC);
+
+/* Check two filters, one of which passes everything */
+int log_test_08(struct unit_test_state *uts)
+{
+ int filt1, filt2;
+
+ filt1 = log_add_filter("console", NULL, LOGL_WARNING, NULL);
+ ut_assert(filt1 >= 0);
+ filt2 = log_add_filter("console", NULL, LOGL_MAX, NULL);
+ ut_assert(filt2 >= 0);
+
+ ut_assertok(console_record_reset_enable());
+ log_run();
+ check_log_entries_extra();
+
+ ut_assertok(log_remove_filter("console", filt1));
+ ut_assertok(log_remove_filter("console", filt2));
+ return 0;
+}
+LOG_TEST_FLAGS(log_test_08, UT_TESTF_CONSOLE_REC);
+
+/* Check three filters, which together pass everything */
+int log_test_09(struct unit_test_state *uts)
+{
+ int filt1, filt2, filt3;
+
+ filt1 = log_add_filter("console", NULL, LOGL_MAX, "file)");
+ ut_assert(filt1 >= 0);
+ filt2 = log_add_filter("console", NULL, LOGL_MAX, "file2");
+ ut_assert(filt2 >= 0);
+ filt3 = log_add_filter("console", NULL, LOGL_MAX, "log/log_test.c");
+ ut_assert(filt3 >= 0);
+
+ ut_assertok(console_record_reset_enable());
+ log_run_file("file2");
+ check_log_entries_extra();
+
+ ut_assertok(log_remove_filter("console", filt1));
+ ut_assertok(log_remove_filter("console", filt2));
+ ut_assertok(log_remove_filter("console", filt3));
+ return 0;
+}
+LOG_TEST_FLAGS(log_test_09, UT_TESTF_CONSOLE_REC);
+
+int do_log_test_10(struct unit_test_state *uts)
+{
+ int i;
+
+ ut_assertok(console_record_reset_enable());
+ log_err("level %d\n", LOGL_EMERG);
+ log_err("level %d\n", LOGL_ALERT);
+ log_err("level %d\n", LOGL_CRIT);
+ log_err("level %d\n", LOGL_ERR);
+ log_warning("level %d\n", LOGL_WARNING);
+ log_notice("level %d\n", LOGL_NOTICE);
+ log_info("level %d\n", LOGL_INFO);
+ log_debug("level %d\n", LOGL_DEBUG);
+ log_content("level %d\n", LOGL_DEBUG_CONTENT);
+ log_io("level %d\n", LOGL_DEBUG_IO);
+
+ for (i = LOGL_EMERG; i <= _LOG_MAX_LEVEL; i++)
+ ut_assert_nextline("%s() level %d", __func__, i);
+ ut_assert_console_end();
+ return 0;
+}
+
+int log_test_10(struct unit_test_state *uts)
 {
- int testnum = 0;
  int ret;
 
- if (argc > 1)
- testnum = simple_strtoul(argv[1], NULL, 10);
-
- ret = log_test(testnum);
- if (ret)
- printf("Test failure (err=%d)\n", ret);
-
- return ret ? CMD_RET_FAILURE : 0;
+ gd->log_fmt = LOGF_TEST;
+ ret = do_log_test_10(uts);
+ gd->log_fmt = log_get_default_format();
+ return ret;
 }
+LOG_TEST_FLAGS(log_test_10, UT_TESTF_CONSOLE_REC);
+
+int do_log_test_11(struct unit_test_state *uts)
+{
+ ut_assertok(console_record_reset_enable());
+ log_err("default\n");
+ ut_assert_nextline("%s() default", __func__);
+
+ ut_assertok(log_device_set_enable(LOG_GET_DRIVER(console), false));
+ log_err("disabled\n");
+
+ ut_assertok(log_device_set_enable(LOG_GET_DRIVER(console), true));
+ log_err("enabled\n");
+ ut_assert_nextline("%s() enabled", __func__);
+ ut_assert_console_end();
+ return 0;
+}
+
+int log_test_11(struct unit_test_state *uts)
+{
+ int ret;
+
+ gd->log_fmt = LOGF_TEST;
+ ret = do_log_test_10(uts);
+ gd->log_fmt = log_get_default_format();
+ return ret;
+}
+LOG_TEST_FLAGS(log_test_11, UT_TESTF_CONSOLE_REC);
diff --git a/test/log/syslog_test.h b/test/log/syslog_test.h
index 1310257bfe..bfaa6daef8 100644
--- a/test/log/syslog_test.h
+++ b/test/log/syslog_test.h
@@ -8,8 +8,6 @@
 #ifndef __SYSLOG_TEST_H
 #define __SYSLOG_TEST_H
 
-#define LOGF_TEST (BIT(LOGF_FUNC) | BIT(LOGF_MSG))
-
 /**
  * struct sb_log_env - private data for sandbox ethernet driver
  *
diff --git a/test/py/tests/test_log.py b/test/py/tests/test_log.py
index 275f9382d2..387b392ce9 100644
--- a/test/py/tests/test_log.py
+++ b/test/py/tests/test_log.py
@@ -10,110 +10,6 @@ and checks that the output is correct.
 
 import pytest
 
-LOGL_FIRST, LOGL_WARNING, LOGL_INFO = (0, 4, 6)
-
-@pytest.mark.buildconfigspec('cmd_log')
-def test_log(u_boot_console):
-    """Test that U-Boot logging works correctly."""
-    def check_log_entries(lines, mask, max_level=LOGL_INFO):
-        """Check that the expected log records appear in the output
-
-        Args:
-            lines: iterator containing lines to check
-            mask: bit mask to select which lines to check for:
-                bit 0: standard log line
-                bit 1: _log line
-            max_level: maximum log level to expect in the output
-        """
-        for i in range(max_level):
-            if mask & 1:
-                assert 'log_run() log %d' % i == next(lines)
-            if mask & 3:
-                assert 'func() _log %d' % i == next(lines)
-
-    def run_test(testnum):
-        """Run a particular test number (the 'log test' command)
-
-        Args:
-            testnum: Test number to run
-        Returns:
-            iterator containing the lines output from the command
-        """
-        output = u_boot_console.run_command('log format fm')
-        assert output == ''
-        with cons.log.section('basic'):
-           output = u_boot_console.run_command('log test %d' % testnum)
-        split = output.replace('\r', '').splitlines()
-        lines = iter(split)
-        assert 'test %d' % testnum == next(lines)
-        return lines
-
-    def test0():
-        lines = run_test(0)
-        check_log_entries(lines, 3)
-
-    def test1():
-        lines = run_test(1)
-        check_log_entries(lines, 3)
-
-    def test2():
-        lines = run_test(2)
-
-    def test3():
-        lines = run_test(3)
-        check_log_entries(lines, 2)
-
-    def test4():
-        lines = run_test(4)
-        assert next(lines, None) == None
-
-    def test5():
-        lines = run_test(5)
-        check_log_entries(lines, 2)
-
-    def test6():
-        lines = run_test(6)
-        check_log_entries(lines, 3)
-
-    def test7():
-        lines = run_test(7)
-        check_log_entries(lines, 3, LOGL_WARNING)
-
-    def test8():
-        lines = run_test(8)
-        check_log_entries(lines, 3)
-
-    def test9():
-        lines = run_test(9)
-        check_log_entries(lines, 3)
-
-    def test10():
-        lines = run_test(10)
-        for i in range(7):
-            assert 'log_test() level %d' % i == next(lines)
-
-    def test11():
-        """Test use of log_device_set_enable()"""
-        lines = run_test(11)
-        assert 'log_test() default'
-        # disabled should not be displayed
-        assert 'log_test() enabled'
-
-    # TODO([hidden email]): Consider structuring this as separate tests
-    cons = u_boot_console
-    test0()
-    test1()
-    test2()
-    test3()
-    test4()
-    test5()
-    test6()
-    test7()
-    test8()
-    test9()
-    test10()
-    test11()
-
 @pytest.mark.buildconfigspec('cmd_log')
 def test_log_format(u_boot_console):
     """Test the 'log format' and 'log rec' commands"""
--
2.28.0

Reply | Threaded
Open this post in threaded view
|

[PATCH v3 10/23] test: log: Give tests names instead of numbers

Sean Anderson
In reply to this post by Sean Anderson
Now that the log test command is no more, we can give the log tests proper
names.

Signed-off-by: Sean Anderson <[hidden email]>
---

Changes in v3:
- New

 test/log/log_test.c | 48 ++++++++++++++++++++++-----------------------
 1 file changed, 24 insertions(+), 24 deletions(-)

diff --git a/test/log/log_test.c b/test/log/log_test.c
index c5ca6dcb7a..4ac378fdad 100644
--- a/test/log/log_test.c
+++ b/test/log/log_test.c
@@ -72,7 +72,7 @@ static int do_check_log_entries(struct unit_test_state *uts, int flags, int min,
 #define check_log_entries_none() check_log_entries_flags(0)
 
 /* Check a category filter using the first category */
-int log_test_00(struct unit_test_state *uts)
+int log_test_cat_allow(struct unit_test_state *uts)
 {
  enum log_category_t cat_list[] = {
  log_uc_cat(UCLASS_MMC), log_uc_cat(UCLASS_SPI),
@@ -94,10 +94,10 @@ int log_test_00(struct unit_test_state *uts)
  ut_assertok(log_remove_filter("console", filt));
  return 0;
 }
-LOG_TEST_FLAGS(log_test_00, UT_TESTF_CONSOLE_REC);
+LOG_TEST_FLAGS(log_test_cat_allow, UT_TESTF_CONSOLE_REC);
 
 /* Check a category filter that should block log entries */
-int log_test_02(struct unit_test_state *uts)
+int log_test_cat_deny_implicit(struct unit_test_state *uts)
 {
  enum log_category_t cat_list[] = {
  log_uc_cat(UCLASS_MMC),  LOGC_NONE, LOGC_END
@@ -114,10 +114,10 @@ int log_test_02(struct unit_test_state *uts)
  ut_assertok(log_remove_filter("console", filt));
  return 0;
 }
-LOG_TEST_FLAGS(log_test_02, UT_TESTF_CONSOLE_REC);
+LOG_TEST_FLAGS(log_test_cat_deny_implicit, UT_TESTF_CONSOLE_REC);
 
 /* Check passing and failing file filters */
-int log_test_03(struct unit_test_state *uts)
+int log_test_file(struct unit_test_state *uts)
 {
  int filt;
 
@@ -135,10 +135,10 @@ int log_test_03(struct unit_test_state *uts)
  ut_assertok(log_remove_filter("console", filt));
  return 0;
 }
-LOG_TEST_FLAGS(log_test_03, UT_TESTF_CONSOLE_REC);
+LOG_TEST_FLAGS(log_test_file, UT_TESTF_CONSOLE_REC);
 
 /* Check a passing file filter (second in list) */
-int log_test_05(struct unit_test_state *uts)
+int log_test_file_second(struct unit_test_state *uts)
 {
  int filt;
 
@@ -152,10 +152,10 @@ int log_test_05(struct unit_test_state *uts)
  ut_assertok(log_remove_filter("console", filt));
  return 0;
 }
-LOG_TEST_FLAGS(log_test_05, UT_TESTF_CONSOLE_REC);
+LOG_TEST_FLAGS(log_test_file_second, UT_TESTF_CONSOLE_REC);
 
 /* Check a passing file filter (middle of list) */
-int log_test_06(struct unit_test_state *uts)
+int log_test_file_mid(struct unit_test_state *uts)
 {
  int filt;
 
@@ -170,10 +170,10 @@ int log_test_06(struct unit_test_state *uts)
  ut_assertok(log_remove_filter("console", filt));
  return 0;
 }
-LOG_TEST_FLAGS(log_test_06, UT_TESTF_CONSOLE_REC);
+LOG_TEST_FLAGS(log_test_file_mid, UT_TESTF_CONSOLE_REC);
 
 /* Check a log level filter */
-int log_test_07(struct unit_test_state *uts)
+int log_test_level(struct unit_test_state *uts)
 {
  int filt;
 
@@ -188,10 +188,10 @@ int log_test_07(struct unit_test_state *uts)
  ut_assertok(log_remove_filter("console", filt));
  return 0;
 }
-LOG_TEST_FLAGS(log_test_07, UT_TESTF_CONSOLE_REC);
+LOG_TEST_FLAGS(log_test_level, UT_TESTF_CONSOLE_REC);
 
 /* Check two filters, one of which passes everything */
-int log_test_08(struct unit_test_state *uts)
+int log_test_double(struct unit_test_state *uts)
 {
  int filt1, filt2;
 
@@ -208,10 +208,10 @@ int log_test_08(struct unit_test_state *uts)
  ut_assertok(log_remove_filter("console", filt2));
  return 0;
 }
-LOG_TEST_FLAGS(log_test_08, UT_TESTF_CONSOLE_REC);
+LOG_TEST_FLAGS(log_test_double, UT_TESTF_CONSOLE_REC);
 
 /* Check three filters, which together pass everything */
-int log_test_09(struct unit_test_state *uts)
+int log_test_triple(struct unit_test_state *uts)
 {
  int filt1, filt2, filt3;
 
@@ -231,9 +231,9 @@ int log_test_09(struct unit_test_state *uts)
  ut_assertok(log_remove_filter("console", filt3));
  return 0;
 }
-LOG_TEST_FLAGS(log_test_09, UT_TESTF_CONSOLE_REC);
+LOG_TEST_FLAGS(log_test_triple, UT_TESTF_CONSOLE_REC);
 
-int do_log_test_10(struct unit_test_state *uts)
+int do_log_test_helpers(struct unit_test_state *uts)
 {
  int i;
 
@@ -255,18 +255,18 @@ int do_log_test_10(struct unit_test_state *uts)
  return 0;
 }
 
-int log_test_10(struct unit_test_state *uts)
+int log_test_helpers(struct unit_test_state *uts)
 {
  int ret;
 
  gd->log_fmt = LOGF_TEST;
- ret = do_log_test_10(uts);
+ ret = do_log_test_helpers(uts);
  gd->log_fmt = log_get_default_format();
  return ret;
 }
-LOG_TEST_FLAGS(log_test_10, UT_TESTF_CONSOLE_REC);
+LOG_TEST_FLAGS(log_test_helpers, UT_TESTF_CONSOLE_REC);
 
-int do_log_test_11(struct unit_test_state *uts)
+int do_log_test_disable(struct unit_test_state *uts)
 {
  ut_assertok(console_record_reset_enable());
  log_err("default\n");
@@ -282,13 +282,13 @@ int do_log_test_11(struct unit_test_state *uts)
  return 0;
 }
 
-int log_test_11(struct unit_test_state *uts)
+int log_test_disable(struct unit_test_state *uts)
 {
  int ret;
 
  gd->log_fmt = LOGF_TEST;
- ret = do_log_test_10(uts);
+ ret = do_log_test_disable(uts);
  gd->log_fmt = log_get_default_format();
  return ret;
 }
-LOG_TEST_FLAGS(log_test_11, UT_TESTF_CONSOLE_REC);
+LOG_TEST_FLAGS(log_test_disable, UT_TESTF_CONSOLE_REC);
--
2.28.0

Reply | Threaded
Open this post in threaded view
|

[PATCH v3 11/23] test: Add tests for LOGFF_DENY

Sean Anderson
In reply to this post by Sean Anderson
This adds some tests for log filters which deny if they match.

Signed-off-by: Sean Anderson <[hidden email]>
Reviewed-by: Simon Glass <[hidden email]>
---

Changes in v3:
- Modified to be completely written in C

 test/log/log_test.c | 67 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 67 insertions(+)

diff --git a/test/log/log_test.c b/test/log/log_test.c
index 4ac378fdad..e4ab999a7d 100644
--- a/test/log/log_test.c
+++ b/test/log/log_test.c
@@ -292,3 +292,70 @@ int log_test_disable(struct unit_test_state *uts)
  return ret;
 }
 LOG_TEST_FLAGS(log_test_disable, UT_TESTF_CONSOLE_REC);
+
+/* Check denying based on category */
+int log_test_cat_deny(struct unit_test_state *uts)
+{
+ int filt1, filt2;
+ enum log_category_t cat_list[] = {
+ log_uc_cat(UCLASS_SPI), LOGC_END
+ };
+
+ filt1 = log_add_filter("console", cat_list, LOGL_MAX, NULL);
+ ut_assert(filt1 >= 0);
+ filt2 = log_add_filter_flags("console", cat_list, LOGL_MAX, NULL,
+     LOGFF_DENY);
+ ut_assert(filt2 >= 0);
+
+ ut_assertok(console_record_reset_enable());
+ log_run_cat(UCLASS_SPI);
+ check_log_entries_none();
+
+ ut_assertok(log_remove_filter("console", filt1));
+ ut_assertok(log_remove_filter("console", filt2));
+ return 0;
+}
+LOG_TEST_FLAGS(log_test_cat_deny, UT_TESTF_CONSOLE_REC);
+
+/* Check denying based on file */
+int log_test_file_deny(struct unit_test_state *uts)
+{
+ int filt1, filt2;
+
+ filt1 = log_add_filter("console", NULL, LOGL_MAX, "file");
+ ut_assert(filt1 >= 0);
+ filt2 = log_add_filter_flags("console", NULL, LOGL_MAX, "file",
+     LOGFF_DENY);
+ ut_assert(filt2 >= 0);
+
+ ut_assertok(console_record_reset_enable());
+ log_run_file("file");
+ check_log_entries_none();
+
+ ut_assertok(log_remove_filter("console", filt1));
+ ut_assertok(log_remove_filter("console", filt2));
+ return 0;
+}
+LOG_TEST_FLAGS(log_test_file_deny, UT_TESTF_CONSOLE_REC);
+
+/* Check denying based on level */
+int log_test_level_deny(struct unit_test_state *uts)
+{
+ int filt1, filt2;
+
+ filt1 = log_add_filter("console", NULL, LOGL_INFO, NULL);
+ ut_assert(filt1 >= 0);
+ filt2 = log_add_filter_flags("console", NULL, LOGL_WARNING, NULL,
+     LOGFF_DENY);
+ ut_assert(filt2 >= 0);
+
+ ut_assertok(console_record_reset_enable());
+ log_run();
+ check_log_entries_flags_levels(EXPECT_LOG | EXPECT_DIRECT,
+       LOGL_WARNING + 1, _LOG_MAX_LEVEL);
+
+ ut_assertok(log_remove_filter("console", filt1));
+ ut_assertok(log_remove_filter("console", filt2));
+ return 0;
+}
+LOG_TEST_FLAGS(log_test_level_deny, UT_TESTF_CONSOLE_REC);
--
2.28.0

Reply | Threaded
Open this post in threaded view
|

[PATCH v3 12/23] log: Add filter flag to match greater than a log level

Sean Anderson
In reply to this post by Sean Anderson
This is the complement of the existing behavior to match only messages with
a log level less than a threshold. This is primarily useful in conjunction
with LOGFF_DENY.

Signed-off-by: Sean Anderson <[hidden email]>
Reviewed-by: Simon Glass <[hidden email]>
---

(no changes since v1)

 common/log.c  | 12 +++++++++---
 include/log.h | 10 ++++++----
 2 files changed, 15 insertions(+), 7 deletions(-)

diff --git a/common/log.c b/common/log.c
index 878800b3bb..259d922987 100644
--- a/common/log.c
+++ b/common/log.c
@@ -159,11 +159,17 @@ static bool log_passes_filters(struct log_device *ldev, struct log_rec *rec)
  }
 
  list_for_each_entry(filt, &ldev->filter_head, sibling_node) {
- if (rec->level > filt->max_level)
+ if (filt->flags & LOGFF_LEVEL_MIN) {
+ if (rec->level < filt->level)
+ continue;
+ } else if (rec->level > filt->level) {
  continue;
+ }
+
  if ((filt->flags & LOGFF_HAS_CAT) &&
     !log_has_cat(filt->cat_list, rec->cat))
  continue;
+
  if (filt->file_list &&
     !log_has_file(filt->file_list, rec->file))
  continue;
@@ -238,7 +244,7 @@ int _log(enum log_category_t cat, enum log_level_t level, const char *file,
 }
 
 int log_add_filter_flags(const char *drv_name, enum log_category_t cat_list[],
- enum log_level_t max_level, const char *file_list,
+ enum log_level_t level, const char *file_list,
  int flags)
 {
  struct log_filter *filt;
@@ -266,7 +272,7 @@ int log_add_filter_flags(const char *drv_name, enum log_category_t cat_list[],
  break;
  }
  }
- filt->max_level = max_level;
+ filt->level = level;
  if (file_list) {
  filt->file_list = strdup(file_list);
  if (!filt->file_list) {
diff --git a/include/log.h b/include/log.h
index 8cabe82eb5..d5e09a838f 100644
--- a/include/log.h
+++ b/include/log.h
@@ -365,6 +365,8 @@ enum log_filter_flags {
  LOGFF_HAS_CAT = 1 << 0,
  /** @LOGFF_DENY: Filter denies matching messages */
  LOGFF_DENY = 1 << 1,
+ /** @LOGFF_LEVEL_MIN: Filter's level is a minimum, not a maximum */
+ LOGFF_LEVEL_MIN = 1 << 2,
 };
 
 /**
@@ -380,7 +382,7 @@ enum log_filter_flags {
  * @cat_list: List of categories to allow (terminated by %LOGC_END). If empty
  * then all categories are permitted. Up to LOGF_MAX_CATEGORIES entries
  * can be provided
- * @max_level: Maximum log level to allow
+ * @level: Maximum (or minimum, if LOGFF_MIN_LEVEL) log level to allow
  * @file_list: List of files to allow, separated by comma. If NULL then all
  * files are permitted
  * @sibling_node: Next filter in the list of filters for this log device
@@ -389,7 +391,7 @@ struct log_filter {
  int filter_num;
  int flags;
  enum log_category_t cat_list[LOGF_MAX_CATEGORIES];
- enum log_level_t max_level;
+ enum log_level_t level;
  const char *file_list;
  struct list_head sibling_node;
 };
@@ -490,14 +492,14 @@ int do_log_test(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
  * @cat_list: List of categories to allow (terminated by %LOGC_END). If empty
  * then all categories are permitted. Up to LOGF_MAX_CATEGORIES entries
  * can be provided
- * @max_level: Maximum log level to allow
+ * @level: Maximum (or minimum, if LOGFF_LEVEL_MIN) log level to allow
  * @file_list: List of files to allow, separated by comma. If NULL then all
  * files are permitted
  * @return the sequence number of the new filter (>=0) if the filter was added,
  * or a -ve value on error
  */
 int log_add_filter_flags(const char *drv_name, enum log_category_t cat_list[],
- enum log_level_t max_level, const char *file_list,
+ enum log_level_t level, const char *file_list,
  int flags);
 
 /**
--
2.28.0

Reply | Threaded
Open this post in threaded view
|

[PATCH v3 13/23] test: Add test for LOGFF_MIN

Sean Anderson
In reply to this post by Sean Anderson
This tests log filters matching on a minimum level.

Signed-off-by: Sean Anderson <[hidden email]>
Reviewed-by: Simon Glass <[hidden email]>
---

Changes in v3:
- Modified to be completely written in C

 test/log/log_test.c | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/test/log/log_test.c b/test/log/log_test.c
index e4ab999a7d..ea4fc6bc30 100644
--- a/test/log/log_test.c
+++ b/test/log/log_test.c
@@ -359,3 +359,26 @@ int log_test_level_deny(struct unit_test_state *uts)
  return 0;
 }
 LOG_TEST_FLAGS(log_test_level_deny, UT_TESTF_CONSOLE_REC);
+
+/* Check matching based on minimum level */
+int log_test_min(struct unit_test_state *uts)
+{
+ int filt1, filt2;
+
+ filt1 = log_add_filter_flags("console", NULL, LOGL_WARNING, NULL,
+     LOGFF_LEVEL_MIN);
+ ut_assert(filt1 >= 0);
+ filt2 = log_add_filter_flags("console", NULL, LOGL_INFO, NULL,
+     LOGFF_DENY | LOGFF_LEVEL_MIN);
+ ut_assert(filt2 >= 0);
+
+ ut_assertok(console_record_reset_enable());
+ log_run();
+ check_log_entries_flags_levels(EXPECT_LOG | EXPECT_DIRECT,
+       LOGL_WARNING, LOGL_INFO - 1);
+
+ ut_assertok(log_remove_filter("console", filt1));
+ ut_assertok(log_remove_filter("console", filt2));
+ return 0;
+}
+LOG_TEST_FLAGS(log_test_min, UT_TESTF_CONSOLE_REC);
--
2.28.0

Reply | Threaded
Open this post in threaded view
|

[PATCH v3 14/23] cmd: log: Use sub-commands for log

Sean Anderson
In reply to this post by Sean Anderson
This reduces duplicate code, and makes adding new sub-commands easier.

Signed-off-by: Sean Anderson <[hidden email]>
Reviewed-by: Simon Glass <[hidden email]>
---

(no changes since v1)

 cmd/log.c | 31 ++++---------------------------
 1 file changed, 4 insertions(+), 27 deletions(-)

diff --git a/cmd/log.c b/cmd/log.c
index d20bfdf744..82e3a7b62f 100644
--- a/cmd/log.c
+++ b/cmd/log.c
@@ -103,30 +103,6 @@ static int do_log_rec(struct cmd_tbl *cmdtp, int flag, int argc,
  return 0;
 }
 
-static struct cmd_tbl log_sub[] = {
- U_BOOT_CMD_MKENT(level, CONFIG_SYS_MAXARGS, 1, do_log_level, "", ""),
- U_BOOT_CMD_MKENT(format, CONFIG_SYS_MAXARGS, 1, do_log_format, "", ""),
- U_BOOT_CMD_MKENT(rec, CONFIG_SYS_MAXARGS, 1, do_log_rec, "", ""),
-};
-
-static int do_log(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
-{
- struct cmd_tbl *cp;
-
- if (argc < 2)
- return CMD_RET_USAGE;
-
- /* drop initial "log" arg */
- argc--;
- argv++;
-
- cp = find_cmd_tbl(argv[0], log_sub, ARRAY_SIZE(log_sub));
- if (cp)
- return cp->cmd(cmdtp, flag, argc, argv);
-
- return CMD_RET_USAGE;
-}
-
 #ifdef CONFIG_SYS_LONGHELP
 static char log_help_text[] =
  "level - get/set log level\n"
@@ -139,7 +115,8 @@ static char log_help_text[] =
  ;
 #endif
 
-U_BOOT_CMD(
- log, CONFIG_SYS_MAXARGS, 1, do_log,
- "log system", log_help_text
+U_BOOT_CMD_WITH_SUBCMDS(log, "log system", log_help_text,
+ U_BOOT_SUBCMD_MKENT(level, 2, 1, do_log_level),
+ U_BOOT_SUBCMD_MKENT(format, 2, 1, do_log_format),
+ U_BOOT_SUBCMD_MKENT(rec, 7, 1, do_log_rec),
 );
--
2.28.0

Reply | Threaded
Open this post in threaded view
|

[PATCH v3 15/23] cmd: log: Split off log level parsing

Sean Anderson
In reply to this post by Sean Anderson
Move parsing of log level into its own function so it can be re-used. This
also adds support for using log level names instead of just the integer
equivalent.

Signed-off-by: Sean Anderson <[hidden email]>
Reviewed-by: Simon Glass <[hidden email]>
---

(no changes since v2)

Changes in v2:
- Print an error message if the log level is invalid.

 cmd/log.c | 29 +++++++++++++++++++++++------
 1 file changed, 23 insertions(+), 6 deletions(-)

diff --git a/cmd/log.c b/cmd/log.c
index 82e3a7b62f..651e50358c 100644
--- a/cmd/log.c
+++ b/cmd/log.c
@@ -11,23 +11,40 @@
 
 static char log_fmt_chars[LOGF_COUNT] = "clFLfm";
 
+static enum log_level_t parse_log_level(char *const arg)
+{
+ enum log_level_t ret;
+ ulong level;
+
+ if (!strict_strtoul(arg, 10, &level)) {
+ if (level > _LOG_MAX_LEVEL) {
+ printf("Only log levels <= %d are supported\n",
+       _LOG_MAX_LEVEL);
+ return LOGL_NONE;
+ }
+ return level;
+ }
+
+ ret = log_get_level_by_name(arg);
+ if (ret == LOGL_NONE)
+ printf("Unknown log level \"%s\"\n", arg);
+ return ret;
+}
+
 static int do_log_level(struct cmd_tbl *cmdtp, int flag, int argc,
  char *const argv[])
 {
  if (argc > 1) {
- long log_level = simple_strtol(argv[1], NULL, 10);
+ enum log_level_t log_level = parse_log_level(argv[1]);
 
- if (log_level < 0 || log_level > _LOG_MAX_LEVEL) {
- printf("Only log levels <= %d are supported\n",
-       _LOG_MAX_LEVEL);
+ if (log_level == LOGL_NONE)
  return CMD_RET_FAILURE;
- }
  gd->default_log_level = log_level;
  } else {
  printf("Default log level: %d\n", gd->default_log_level);
  }
 
- return 0;
+ return CMD_RET_SUCCESS;
 }
 
 static int do_log_format(struct cmd_tbl *cmdtp, int flag, int argc,
--
2.28.0

Reply | Threaded
Open this post in threaded view
|

[PATCH v3 16/23] cmd: log: Add commands to list categories and drivers

Sean Anderson
In reply to this post by Sean Anderson
This allows users to query which categories and drivers are available on
their system. This allows them to construct filter-add commands without
(e.g.) adjusting the log format to show categories and drivers.

Signed-off-by: Sean Anderson <[hidden email]>
Reviewed-by: Simon Glass <[hidden email]>
---

Changes in v3:
- Document assumption that erroneous results from log_get_cat_name begin with
  '<'

Changes in v2:
- New

 cmd/log.c     | 35 +++++++++++++++++++++++++++++++++++
 common/log.c  |  1 +
 include/log.h |  5 +++--
 3 files changed, 39 insertions(+), 2 deletions(-)

diff --git a/cmd/log.c b/cmd/log.c
index 651e50358c..8d8d8a8172 100644
--- a/cmd/log.c
+++ b/cmd/log.c
@@ -47,6 +47,37 @@ static int do_log_level(struct cmd_tbl *cmdtp, int flag, int argc,
  return CMD_RET_SUCCESS;
 }
 
+static int do_log_categories(struct cmd_tbl *cmdtp, int flag, int argc,
+     char *const argv[])
+{
+ enum log_category_t cat;
+ const char *name;
+
+ for (cat = LOGC_FIRST; cat < LOGC_COUNT; cat++) {
+ name = log_get_cat_name(cat);
+ /*
+ * Invalid category names (e.g. <invalid> or <missing>) begin
+ * with '<'.
+ */
+ if (name[0] == '<')
+ continue;
+ printf("%s\n", name);
+ }
+
+ return CMD_RET_SUCCESS;
+}
+
+static int do_log_drivers(struct cmd_tbl *cmdtp, int flag, int argc,
+  char *const argv[])
+{
+ struct log_device *ldev;
+
+ list_for_each_entry(ldev, &gd->log_head, sibling_node)
+ printf("%s\n", ldev->drv->name);
+
+ return CMD_RET_SUCCESS;
+}
+
 static int do_log_format(struct cmd_tbl *cmdtp, int flag, int argc,
  char *const argv[])
 {
@@ -123,6 +154,8 @@ static int do_log_rec(struct cmd_tbl *cmdtp, int flag, int argc,
 #ifdef CONFIG_SYS_LONGHELP
 static char log_help_text[] =
  "level - get/set log level\n"
+ "categories - list log categories\n"
+ "drivers - list log drivers\n"
  "log format <fmt> - set log output format. <fmt> is a string where\n"
  "\teach letter indicates something that should be displayed:\n"
  "\tc=category, l=level, F=file, L=line number, f=function, m=msg\n"
@@ -134,6 +167,8 @@ static char log_help_text[] =
 
 U_BOOT_CMD_WITH_SUBCMDS(log, "log system", log_help_text,
  U_BOOT_SUBCMD_MKENT(level, 2, 1, do_log_level),
+ U_BOOT_SUBCMD_MKENT(categories, 1, 1, do_log_categories),
+ U_BOOT_SUBCMD_MKENT(drivers, 1, 1, do_log_drivers),
  U_BOOT_SUBCMD_MKENT(format, 2, 1, do_log_format),
  U_BOOT_SUBCMD_MKENT(rec, 7, 1, do_log_rec),
 );
diff --git a/common/log.c b/common/log.c
index 259d922987..c4a14e191a 100644
--- a/common/log.c
+++ b/common/log.c
@@ -41,6 +41,7 @@ static const char *const log_level_name[LOGL_COUNT] = {
  "IO",
 };
 
+/* All error responses MUST begin with '<' */
 const char *log_get_cat_name(enum log_category_t cat)
 {
  const char *name;
diff --git a/include/log.h b/include/log.h
index d5e09a838f..9f57957aae 100644
--- a/include/log.h
+++ b/include/log.h
@@ -407,8 +407,9 @@ struct log_filter {
  * log_get_cat_name() - Get the name of a category
  *
  * @cat: Category to look up
- * @return category name (which may be a uclass driver name) if found, or
- * "<invalid>" if invalid, or "<missing>" if not found
+ * @return: category name (which may be a uclass driver name) if found, or
+ *   "<invalid>" if invalid, or "<missing>" if not found. All error
+ *   responses begin with '<'.
  */
 const char *log_get_cat_name(enum log_category_t cat);
 
--
2.28.0

Reply | Threaded
Open this post in threaded view
|

[PATCH v3 17/23] cmd: log: Make "log level" print all log levels

Sean Anderson
In reply to this post by Sean Anderson
This makes the log level command print all valid log levels. The default
log level is annotated. This provides an easy way to see which log levels
are compiled-in.

Signed-off-by: Sean Anderson <[hidden email]>
Reviewed-by: Simon Glass <[hidden email]>
---

(no changes since v2)

Changes in v2:
- New

 cmd/log.c | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/cmd/log.c b/cmd/log.c
index 8d8d8a8172..596bc73f47 100644
--- a/cmd/log.c
+++ b/cmd/log.c
@@ -34,14 +34,20 @@ static enum log_level_t parse_log_level(char *const arg)
 static int do_log_level(struct cmd_tbl *cmdtp, int flag, int argc,
  char *const argv[])
 {
+ enum log_level_t log_level;
+
  if (argc > 1) {
- enum log_level_t log_level = parse_log_level(argv[1]);
+ log_level = parse_log_level(argv[1]);
 
  if (log_level == LOGL_NONE)
  return CMD_RET_FAILURE;
  gd->default_log_level = log_level;
  } else {
- printf("Default log level: %d\n", gd->default_log_level);
+ for (log_level = LOGL_FIRST; log_level <= _LOG_MAX_LEVEL;
+     log_level++)
+ printf("%s%s\n", log_get_level_name(log_level),
+       log_level == gd->default_log_level ?
+       " (default)" : "");
  }
 
  return CMD_RET_SUCCESS;
@@ -153,7 +159,7 @@ static int do_log_rec(struct cmd_tbl *cmdtp, int flag, int argc,
 
 #ifdef CONFIG_SYS_LONGHELP
 static char log_help_text[] =
- "level - get/set log level\n"
+ "level [<level>] - get/set log level\n"
  "categories - list log categories\n"
  "drivers - list log drivers\n"
  "log format <fmt> - set log output format. <fmt> is a string where\n"
--
2.28.0

Reply | Threaded
Open this post in threaded view
|

[PATCH v3 18/23] lib: Add getopt

Sean Anderson
In reply to this post by Sean Anderson
Some commands can get very unweildy if they have too many positional
arguments. Adding options makes them easier to read, remember, and
understand.

This implementation of getopt has been taken from barebox, which has had
option support for quite a while. I have made a few modifications to their
version, such as the removal of opterr in favor of a separate getopt_silent
function. In addition, I have moved all global variables into struct
getopt_context.

The getopt from barebox also re-orders the arguments passed to it so that
non-options are placed last. This allows users to specify options anywhere.
For example, `ls -l foo/ -R` would be re-ordered to `ls -l -R foo/` as
getopt parsed the options. However, this feature conflicts with the const
argv in cmd_tbl->cmd. This was originally added in 54841ab50c ("Make sure
that argv[] argument pointers are not modified."). The reason stated in
that commit is that hush requires argv to stay unmodified. Has this
situation changed? Barebox also uses hush, and does not have this problem.
Perhaps we could use their fix?

I have assigned maintenance of getopt to Simon Glass, as it is currently
only used by the log command. I would also be fine maintaining it.

Signed-off-by: Sean Anderson <[hidden email]>
---

(no changes since v2)

Changes in v2:
- Expand documentation of getopt() to include examples
- Remove opt prefix from getopt_state members

 MAINTAINERS        |   1 +
 doc/api/getopt.rst |   8 +++
 doc/api/index.rst  |   1 +
 include/getopt.h   | 130 +++++++++++++++++++++++++++++++++++++++++++++
 lib/Kconfig        |   5 ++
 lib/Makefile       |   1 +
 lib/getopt.c       | 125 +++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 271 insertions(+)
 create mode 100644 doc/api/getopt.rst
 create mode 100644 include/getopt.h
 create mode 100644 lib/getopt.c

diff --git a/MAINTAINERS b/MAINTAINERS
index fb9ba37984..0f8e1f6d4c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -747,6 +747,7 @@ T: git https://gitlab.denx.de/u-boot/u-boot.git
 F: common/log*
 F: cmd/log.c
 F: doc/develop/logging.rst
+F: lib/getopt.c
 F: test/log/
 F: test/py/tests/test_log.py
 
diff --git a/doc/api/getopt.rst b/doc/api/getopt.rst
new file mode 100644
index 0000000000..773f79aeb6
--- /dev/null
+++ b/doc/api/getopt.rst
@@ -0,0 +1,8 @@
+.. SPDX-License-Identifier: GPL-2.0+
+.. Copyright (C) 2020 Sean Anderson <[hidden email]>
+
+Option Parsing
+==============
+
+.. kernel-doc:: include/getopt.h
+   :internal:
diff --git a/doc/api/index.rst b/doc/api/index.rst
index 1c261bcb73..d90e70e16a 100644
--- a/doc/api/index.rst
+++ b/doc/api/index.rst
@@ -8,6 +8,7 @@ U-Boot API documentation
 
    dfu
    efi
+   getopt
    linker_lists
    pinctrl
    rng
diff --git a/include/getopt.h b/include/getopt.h
new file mode 100644
index 0000000000..6f5811e64b
--- /dev/null
+++ b/include/getopt.h
@@ -0,0 +1,130 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * getopt.h - a simple getopt(3) implementation.
+ *
+ * Copyright (C) 2020 Sean Anderson <[hidden email]>
+ * Copyright (c) 2007 Sascha Hauer <[hidden email]>, Pengutronix
+ */
+
+#ifndef __GETOPT_H
+#define __GETOPT_H
+
+/**
+ * struct getopt_state - Saved state across getopt() calls
+ */
+struct getopt_state {
+ /**
+ * @index: Index of the next unparsed argument of @argv. If getopt() has
+ * parsed all of @argv, then @index will equal @argc.
+ */
+ int index;
+ /* private: */
+ /** @arg_index: Index within the current argument */
+ int arg_index;
+ union {
+ /* public: */
+ /**
+ * @opt: Option being parsed when an error occurs. @opt is only
+ * valid when getopt() returns ``?`` or ``:``.
+ */
+ int opt;
+ /**
+ * @arg: The argument to an option, NULL if there is none. @arg
+ * is only valid when getopt() returns an option character.
+ */
+ char *arg;
+ /* private: */
+ };
+};
+
+/**
+ * getopt_init_state() - Initialize a &struct getopt_state
+ * @gs: The state to initialize
+ *
+ * This must be called before using @gs with getopt().
+ */
+void getopt_init_state(struct getopt_state *gs);
+
+int __getopt(struct getopt_state *gs, int argc, char *const argv[],
+     const char *optstring, bool silent);
+
+/**
+ * getopt() - Parse short command-line options
+ * @gs: Internal state and out-of-band return arguments. This must be
+ *      initialized with getopt_init_context() beforehand.
+ * @argc: Number of arguments, not including the %NULL terminator
+ * @argv: Argument list, terminated by %NULL
+ * @optstring: Option specification, as described below
+ *
+ * getopt() parses short options. Short options are single characters. They may
+ * be followed by a required argument or an optional argument. Arguments to
+ * options may occur in the same argument as an option (like ``-larg``), or
+ * in the following argument (like ``-l arg``). An argument containing
+ * options begins with a ``-``. If an option expects no arguments, then it may
+ * be immediately followed by another option (like ``ls -alR``).
+ *
+ * @optstring is a list of accepted options. If an option is followed by ``:``
+ * in @optstring, then it expects a mandatory argument. If an option is followed
+ * by ``::`` in @optstring, it expects an optional argument. @gs.arg points
+ * to the argument, if one is parsed.
+ *
+ * getopt() stops parsing options when it encounters the first non-option
+ * argument, when it encounters the argument ``--``, or when it runs out of
+ * arguments. For example, in ``ls -l foo -R``, option parsing will stop when
+ * getopt() encounters ``foo``, if ``l`` does not expect an argument. However,
+ * the whole list of arguments would be parsed if ``l`` expects an argument.
+ *
+ * An example invocation of getopt() might look like::
+ *
+ *     char *argv[] = { "program", "-cbx", "-a", "foo", "bar", 0 };
+ *     int opt, argc = ARRAY_SIZE(argv) - 1;
+ *     struct getopt_state gs;
+ *
+ *     getopt_init_state(&gs);
+ *     while ((opt = getopt(&gs, argc, argv, "a::b:c")) != -1)
+ *         printf("opt = %c, index = %d, arg = \"%s\"\n", opt, gs.index, gs.arg);
+ *     printf("%d argument(s) left\n", argc - gs.index);
+ *
+ * and would produce an output of::
+ *
+ *     opt = c, index = 1, arg = "<NULL>"
+ *     opt = b, index = 2, arg = "x"
+ *     opt = a, index = 4, arg = "foo"
+ *     1 argument(s) left
+ *
+ * For further information, refer to the getopt(3) man page.
+ *
+ * Return:
+ * * An option character if an option is found. @gs.arg is set to the
+ *   argument if there is one, otherwise it is set to ``NULL``.
+ * * ``-1`` if there are no more options, if a non-option argument is
+ *   encountered, or if an ``--`` argument is encountered.
+ * * ``'?'`` if we encounter an option not in @optstring. @gs.opt is set to
+ *   the unknown option.
+ * * ``':'`` if an argument is required, but no argument follows the
+ *   option. @gs.opt is set to the option missing its argument.
+ *
+ * @gs.index is always set to the index of the next unparsed argument in @argv.
+ */
+static inline int getopt(struct getopt_state *gs, int argc,
+ char *const argv[], const char *optstring)
+{
+ return __getopt(gs, argc, argv, optstring, false);
+}
+
+/**
+ * getopt_silent() - Parse short command-line options silently
+ * @gs: State
+ * @argc: Argument count
+ * @argv: Argument list
+ * @optstring: Option specification
+ *
+ * Same as getopt(), except no error messages are printed.
+ */
+static inline int getopt_silent(struct getopt_state *gs, int argc,
+ char *const argv[], const char *optstring)
+{
+ return __getopt(gs, argc, argv, optstring, true);
+}
+
+#endif /* __GETOPT_H */
diff --git a/lib/Kconfig b/lib/Kconfig
index 37aae73a26..79651eaad1 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -550,6 +550,11 @@ config SPL_HEXDUMP
   This enables functions for printing dumps of binary data in
   SPL.
 
+config GETOPT
+ bool "Enable getopt"
+ help
+  This enables functions for parsing command-line options.
+
 config OF_LIBFDT
  bool "Enable the FDT library"
  default y if OF_CONTROL
diff --git a/lib/Makefile b/lib/Makefile
index 0cd7bea282..7c7fb9aae7 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -106,6 +106,7 @@ obj-y += string.o
 obj-y += tables_csum.o
 obj-y += time.o
 obj-y += hexdump.o
+obj-$(CONFIG_GETOPT) += getopt.o
 obj-$(CONFIG_TRACE) += trace.o
 obj-$(CONFIG_LIB_UUID) += uuid.o
 obj-$(CONFIG_LIB_RAND) += rand.o
diff --git a/lib/getopt.c b/lib/getopt.c
new file mode 100644
index 0000000000..8b4515dc19
--- /dev/null
+++ b/lib/getopt.c
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * getopt.c - a simple getopt(3) implementation. See getopt.h for explanation.
+ *
+ * Copyright (C) 2020 Sean Anderson <[hidden email]>
+ * Copyright (c) 2007 Sascha Hauer <[hidden email]>, Pengutronix
+ */
+
+#define LOG_CATEGORY LOGC_CORE
+
+#include <common.h>
+#include <getopt.h>
+#include <log.h>
+
+void getopt_init_state(struct getopt_state *gs)
+{
+ gs->index = 1;
+ gs->arg_index = 1;
+}
+
+int __getopt(struct getopt_state *gs, int argc, char *const argv[],
+     const char *optstring, bool silent)
+{
+ char curopt;   /* current option character */
+ const char *curoptp; /* pointer to the current option in optstring */
+
+ while (1) {
+ log_debug("arg_index: %d index: %d\n", gs->arg_index,
+  gs->index);
+
+ /* `--` indicates the end of options */
+ if (gs->arg_index == 1 && argv[gs->index] &&
+    !strcmp(argv[gs->index], "--")) {
+ gs->index++;
+ return -1;
+ }
+
+ /* Out of arguments */
+ if (gs->index >= argc)
+ return -1;
+
+ /* Can't parse non-options */
+ if (*argv[gs->index] != '-')
+ return -1;
+
+ /* We have found an option */
+ curopt = argv[gs->index][gs->arg_index];
+ if (curopt)
+ break;
+ /*
+ * no more options in current argv[] element; try the next one
+ */
+ gs->index++;
+ gs->arg_index = 1;
+ }
+
+ /* look up current option in optstring */
+ curoptp = strchr(optstring, curopt);
+
+ if (!curoptp) {
+ if (!silent)
+ printf("%s: invalid option -- %c\n", argv[0], curopt);
+ gs->opt = curopt;
+ gs->arg_index++;
+ return '?';
+ }
+
+ if (*(curoptp + 1) != ':') {
+ /* option with no argument. Just return it */
+ gs->arg = NULL;
+ gs->arg_index++;
+ return curopt;
+ }
+
+ if (*(curoptp + 1) && *(curoptp + 2) == ':') {
+ /* optional argument */
+ if (argv[gs->index][gs->arg_index + 1]) {
+ /* optional argument with directly following arg */
+ gs->arg = argv[gs->index++] + gs->arg_index + 1;
+ gs->arg_index = 1;
+ return curopt;
+ }
+ if (gs->index + 1 == argc) {
+ /* We are at the last argv[] element */
+ gs->arg = NULL;
+ gs->index++;
+ return curopt;
+ }
+ if (*argv[gs->index + 1] != '-') {
+ /*
+ * optional argument with arg in next argv[] element
+ */
+ gs->index++;
+ gs->arg = argv[gs->index++];
+ gs->arg_index = 1;
+ return curopt;
+ }
+
+ /* no optional argument found */
+ gs->arg = NULL;
+ gs->arg_index = 1;
+ gs->index++;
+ return curopt;
+ }
+
+ if (argv[gs->index][gs->arg_index + 1]) {
+ /* required argument with directly following arg */
+ gs->arg = argv[gs->index++] + gs->arg_index + 1;
+ gs->arg_index = 1;
+ return curopt;
+ }
+
+ gs->index++;
+ gs->arg_index = 1;
+
+ if (gs->index >= argc || argv[gs->index][0] == '-') {
+ if (!silent)
+ printf("option requires an argument -- %c\n", curopt);
+ gs->opt = curopt;
+ return ':';
+ }
+
+ gs->arg = argv[gs->index++];
+ return curopt;
+}
--
2.28.0

Reply | Threaded
Open this post in threaded view
|

[PATCH v3 19/23] test: Add a test for getopt

Sean Anderson
In reply to this post by Sean Anderson
A few of these tests were inspired by those in glibc. The syntax for
invoking test_getopt is a bit funky, but it's necessary so that the CPP can
parse the arguments correctly.

Signed-off-by: Sean Anderson <[hidden email]>
---

(no changes since v1)

 test/lib/Makefile |   1 +
 test/lib/getopt.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 124 insertions(+)
 create mode 100644 test/lib/getopt.c

diff --git a/test/lib/Makefile b/test/lib/Makefile
index 22236f8587..3140c2dad7 100644
--- a/test/lib/Makefile
+++ b/test/lib/Makefile
@@ -13,3 +13,4 @@ obj-$(CONFIG_ERRNO_STR) += test_errno_str.o
 obj-$(CONFIG_UT_LIB_ASN1) += asn1.o
 obj-$(CONFIG_UT_LIB_RSA) += rsa.o
 obj-$(CONFIG_AES) += test_aes.o
+obj-$(CONFIG_GETOPT) += getopt.o
diff --git a/test/lib/getopt.c b/test/lib/getopt.c
new file mode 100644
index 0000000000..3c68b93c8a
--- /dev/null
+++ b/test/lib/getopt.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2020 Sean Anderson <[hidden email]>
+ *
+ * Portions of these tests were inspired by glibc's posix/bug-getopt1.c and
+ * posix/tst-getopt-cancel.c
+ */
+
+#include <common.h>
+#include <getopt.h>
+#include <test/lib.h>
+#include <test/test.h>
+#include <test/ut.h>
+
+static int do_test_getopt(struct unit_test_state *uts, int line,
+  struct getopt_state *gs, const char *optstring,
+  int args, char *argv[], int expected_count,
+  int expected[])
+{
+ int opt;
+
+ getopt_init_state(gs);
+ for (int i = 0; i < expected_count; i++) {
+ opt = getopt_silent(gs, args, argv, optstring);
+ if (expected[i] != opt) {
+ /*
+ * Fudge the line number so we can tell which test
+ * failed
+ */
+ ut_failf(uts, __FILE__, line, __func__,
+ "expected[i] == getopt()",
+ "Expected '%c' (%d) with i=%d, got '%c' (%d)",
+ expected[i], expected[i], i, opt, opt);
+ return CMD_RET_FAILURE;
+ }
+ }
+
+ opt = getopt_silent(gs, args, argv, optstring);
+ if (opt != -1) {
+ ut_failf(uts, __FILE__, line, __func__,
+ "getopt() != -1",
+ "Expected -1, got '%c' (%d)", opt, opt);
+ return CMD_RET_FAILURE;
+ }
+
+ return 0;
+}
+
+#define test_getopt(optstring, argv, expected) do { \
+ int ret = do_test_getopt(uts, __LINE__, &gs, optstring, \
+ ARRAY_SIZE(argv) - 1, argv, \
+ ARRAY_SIZE(expected), expected); \
+ if (ret) \
+ return ret; \
+} while (0)
+
+static int lib_test_getopt(struct unit_test_state *uts)
+{
+ struct getopt_state gs;
+
+ /* Happy path */
+ test_getopt("ab:c",
+    ((char *[]){ "program", "-cb", "x", "-a", "foo", 0 }),
+    ((int []){ 'c', 'b', 'a' }));
+ ut_asserteq(4, gs.index);
+
+ /* Make sure we pick up the optional argument */
+ test_getopt("a::b:c",
+    ((char *[]){ "program", "-cbx", "-a", "foo", 0 }),
+    ((int []){ 'c', 'b', 'a' }));
+ ut_asserteq(4, gs.index);
+
+ /* Test required arguments */
+ test_getopt("a:b", ((char *[]){ "program", "-a", 0 }),
+    ((int []){ ':' }));
+ ut_asserteq('a', gs.opt);
+ test_getopt("a:b", ((char *[]){ "program", "-b", "-a", 0 }),
+    ((int []){ 'b', ':' }));
+ ut_asserteq('a', gs.opt);
+
+ /* Test invalid arguments */
+ test_getopt("ab:c", ((char *[]){ "program", "-d", 0 }),
+    ((int []){ '?' }));
+ ut_asserteq('d', gs.opt);
+
+ /* Test arg */
+ test_getopt("a::b:c",
+    ((char *[]){ "program", "-a", 0 }),
+    ((int []){ 'a' }));
+ ut_asserteq(2, gs.index);
+ ut_assertnull(gs.arg);
+
+ test_getopt("a::b:c",
+    ((char *[]){ "program", "-afoo", 0 }),
+    ((int []){ 'a' }));
+ ut_asserteq(2, gs.index);
+ ut_assertnonnull(gs.arg);
+ ut_asserteq_str("foo", gs.arg);
+
+ test_getopt("a::b:c",
+    ((char *[]){ "program", "-a", "foo", 0 }),
+    ((int []){ 'a' }));
+ ut_asserteq(3, gs.index);
+ ut_assertnonnull(gs.arg);
+ ut_asserteq_str("foo", gs.arg);
+
+ test_getopt("a::b:c",
+    ((char *[]){ "program", "-bfoo", 0 }),
+    ((int []){ 'b' }));
+ ut_asserteq(2, gs.index);
+ ut_assertnonnull(gs.arg);
+ ut_asserteq_str("foo", gs.arg);
+
+ test_getopt("a::b:c",
+    ((char *[]){ "program", "-b", "foo", 0 }),
+    ((int []){ 'b' }));
+ ut_asserteq(3, gs.index);
+ ut_assertnonnull(gs.arg);
+ ut_asserteq_str("foo", gs.arg);
+
+ return 0;
+}
+LIB_TEST(lib_test_getopt, 0);
--
2.28.0

12