All of lore.kernel.org
 help / color / mirror / Atom feed
* [LTP] [PATCH v2 0/5] Another attempt at hardware discovery
@ 2021-02-24 16:50 Cyril Hrubis
  2021-02-24 16:50 ` [LTP] [PATCH v2 1/5] lib: tst_cmd: Make tst_cmd() usable for global paths Cyril Hrubis
                   ` (6 more replies)
  0 siblings, 7 replies; 15+ messages in thread
From: Cyril Hrubis @ 2021-02-24 16:50 UTC (permalink / raw)
  To: ltp

This is a second attempt on hardware discovery LTP support. The main
difference between the previous attempts is that this version uses JSON,
which allows us propagate structured data to the test.

This is still an early protototype but I'm seding it out to get more
feedback before I continue to work on it.

So how is this supposed to work:

* Test that needs particular hardware sets the needs_hardware filed in
  the test test structure. This is a free form string, in the uart
  example it's called 'UART-loopback'. If we ever add a test for i2c
  eeprom it would be probably called 'I2C-eeprom', etc.

* The test library takes this and passes it to the hardware discovery
  script/binary. The example scipt that is included in this patchset
  just hardcodes configuration for a usb-to-serial cable. In real world
  lab this would be either prepared for each board specifically and
  injected to the filesystem before the test happens, or may be a simple
  script that calls curl with a request to a lab sever, etc.

* The output from the script is a JSON object. If there is a need to
  reconfigure lab hardware before the test, the JSON contains a path to
  a script/binary that has to be called to do so. Again this may be a
  script that calls curl with a request to a lab sever which would, for
  example, interconnect different serial ports with relays.

  The parameter to that script is an unique ID for the hardware
  configuration that is listed in each hardware configuration in the
  hwconfs array of objects that follows.

  I'm not sure if this actually belongs there, maybe it would be
  cleaner to have one reconfigure script for the whole LTP and we would
  pass the needs_hardware content as well as the unique ID, e.g.

  'hardware-reconfigure.sh UART-loopback ttyUSB0-ttyS0'

  but that is a minor detail that could be easily sorted out later.


  The most important part of the JSON is the hwconfs array, which
  consists of hardware description objects, which, apart form the uid,
  are not interpreted by the library, but rather passed to the test. The
  test library loops over the array and forks a testrun for each entry
  in the array.

  Each iteration of the test then gets it's parameters as a JSON object.
  In the case of the UART one of the objects in the array looks like:

  {
    "uid": "ttyUSB0-ttyUSB0-01",
    "rx": "ttyUSB0",
    "tx": "ttyUSB0",
    "hwflow": 0,
    "baud_rates": [
     9600,
     19200
    ]
  }

  Which is mostly self-explanatory, the test then parses the structure
  and executes one test per each baud rate.

  What is still missing is the ability to pass the JSON hardware
  description directly to the test, so that we can execute the test
  manually, but that would be fairly easy to add.

Cyril Hrubis (5):
  lib: tst_cmd: Make tst_cmd() usable for global paths
  lib: Add minimalistic json parser
  lib: Add hardware discovery code
  Sample hardware discovery and reconfigure scripts
  testcases: uart01: Add.

 hardware-discovery.sh                         |  36 +
 hardware-reconfigure.sh                       |   3 +
 include/tst_hwconf.h                          |  13 +
 include/tst_json.h                            | 177 +++++
 include/tst_test.h                            |   3 +
 lib/tst_cmd.c                                 |   2 +-
 lib/tst_hardware.c                            | 218 ++++++
 lib/tst_hardware.h                            |  83 +++
 lib/tst_json.c                                | 679 ++++++++++++++++++
 lib/tst_test.c                                |  30 +
 runtest/device_drivers                        |   2 +
 testcases/kernel/device-drivers/Makefile      |   1 +
 .../kernel/device-drivers/uart/.gitignore     |   1 +
 testcases/kernel/device-drivers/uart/Makefile |   3 +
 testcases/kernel/device-drivers/uart/uart01.c | 620 ++++++++++++++++
 15 files changed, 1870 insertions(+), 1 deletion(-)
 create mode 100755 hardware-discovery.sh
 create mode 100755 hardware-reconfigure.sh
 create mode 100644 include/tst_hwconf.h
 create mode 100644 include/tst_json.h
 create mode 100644 lib/tst_hardware.c
 create mode 100644 lib/tst_hardware.h
 create mode 100644 lib/tst_json.c
 create mode 100644 runtest/device_drivers
 create mode 100644 testcases/kernel/device-drivers/uart/.gitignore
 create mode 100644 testcases/kernel/device-drivers/uart/Makefile
 create mode 100644 testcases/kernel/device-drivers/uart/uart01.c

-- 
2.26.2


^ permalink raw reply	[flat|nested] 15+ messages in thread

* [LTP] [PATCH v2 1/5] lib: tst_cmd: Make tst_cmd() usable for global paths
  2021-02-24 16:50 [LTP] [PATCH v2 0/5] Another attempt at hardware discovery Cyril Hrubis
@ 2021-02-24 16:50 ` Cyril Hrubis
  2021-02-24 17:57   ` [LTP] [Automated-testing] " Petr Vorel
  2021-02-24 16:50 ` [LTP] [PATCH v2 2/5] lib: Add minimalistic json parser Cyril Hrubis
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 15+ messages in thread
From: Cyril Hrubis @ 2021-02-24 16:50 UTC (permalink / raw)
  To: ltp

We need to skip the check if the path to the binary starts with '/'.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 lib/tst_cmd.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/tst_cmd.c b/lib/tst_cmd.c
index 7446249f9..b73978e20 100644
--- a/lib/tst_cmd.c
+++ b/lib/tst_cmd.c
@@ -58,7 +58,7 @@ int tst_cmd_fds_(void (cleanup_fn)(void),
 
 	char path[PATH_MAX];
 
-	if (tst_get_path(argv[0], path, sizeof(path))) {
+	if (argv[0][0] != '/' && tst_get_path(argv[0], path, sizeof(path))) {
 		if (flags & TST_CMD_TCONF_ON_MISSING)
 			tst_brkm(TCONF, cleanup_fn, "Couldn't find '%s' in $PATH at %s:%d", argv[0],
 				 __FILE__, __LINE__);
-- 
2.26.2


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [LTP] [PATCH v2 2/5] lib: Add minimalistic json parser
  2021-02-24 16:50 [LTP] [PATCH v2 0/5] Another attempt at hardware discovery Cyril Hrubis
  2021-02-24 16:50 ` [LTP] [PATCH v2 1/5] lib: tst_cmd: Make tst_cmd() usable for global paths Cyril Hrubis
@ 2021-02-24 16:50 ` Cyril Hrubis
  2021-03-01  7:38   ` [LTP] [Automated-testing] " Petr Vorel
  2021-02-24 16:50 ` [LTP] [PATCH v2 3/5] lib: Add hardware discovery code Cyril Hrubis
                   ` (4 subsequent siblings)
  6 siblings, 1 reply; 15+ messages in thread
From: Cyril Hrubis @ 2021-02-24 16:50 UTC (permalink / raw)
  To: ltp

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 include/tst_json.h | 177 ++++++++++++
 lib/tst_json.c     | 679 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 856 insertions(+)
 create mode 100644 include/tst_json.h
 create mode 100644 lib/tst_json.c

diff --git a/include/tst_json.h b/include/tst_json.h
new file mode 100644
index 000000000..4b3669824
--- /dev/null
+++ b/include/tst_json.h
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * Copyright (C) 2021 Cyril Hrubis <metan@ucw.cz>
+ */
+
+#ifndef TST_JSON_H
+#define TST_JSON_H
+
+#include <stdio.h>
+
+#define TST_JSON_ERR_MAX 128
+#define TST_JSON_ID_MAX 64
+
+enum tst_json_type {
+	TST_JSON_VOID = 0,
+	TST_JSON_INT,
+	TST_JSON_STR,
+	TST_JSON_OBJ,
+	TST_JSON_ARR,
+};
+
+struct tst_json_buf {
+	/** Pointer to a null terminated JSON string */
+	const char *json;
+	/** A length of the JSON string */
+	size_t len;
+	/** A current offset into the JSON string */
+	size_t off;
+	/** An offset to the start of the last array or object */
+	size_t sub_off;
+
+	char err[TST_JSON_ERR_MAX];
+	char buf[];
+};
+
+struct tst_json_val {
+	enum tst_json_type type;
+
+	/** An user supplied buffer and size to store a string values to. */
+	char *buf;
+	size_t buf_size;
+
+	/** An union to store the parsed value into. */
+	union {
+		long val_int;
+		const char *val_str;
+	};
+
+	/** An ID for object values */
+	char id[TST_JSON_ID_MAX];
+};
+
+/*
+ * @brief Resets the parser.
+ *
+ * Resets the parse offset and clears errors.
+ *
+ * @buf An tst_json buffer
+ */
+static inline void tst_json_reset(struct tst_json_buf *buf)
+{
+	buf->off = 0;
+	buf->err[0] = 0;
+}
+
+/*
+ * @brief Fills the buffer error.
+ *
+ * Once buffer error is set all parsing functions return immediatelly with type
+ * set to TST_JSON_VOID.
+ *
+ * @buf An tst_json buffer
+ * @fmt A printf like format string
+ * @... A printf like parameters
+ */
+void tst_json_err(struct tst_json_buf *buf, const char *fmt, ...)
+               __attribute__((format (printf, 2, 3)));
+
+/*
+ * @brief Prints error into a file.
+ *
+ * The error takes into consideration the current offset in the buffer and
+ * prints a few preceding lines along with the exact position of the error.
+ *
+ * @f A file to print the error to.
+ * @buf An tst_json buffer.
+ */
+void tst_json_err_print(FILE *f, struct tst_json_buf *buf);
+
+/*
+ * @brief Returns true if error was encountered.
+ *
+ * @bfu An tst_json buffer.
+ * @return True if error was encountered false otherwise.
+ */
+static inline int tst_json_is_err(struct tst_json_buf *buf)
+{
+	return !!buf->err[0];
+}
+
+/*
+ * @brief Checks is result has valid type.
+ *
+ * @res An tst_json value.
+ * @return Zero if result is not valid, non-zero otherwise.
+ */
+static inline int tst_json_valid(struct tst_json_val *res)
+{
+	return !!res->type;
+}
+
+/*
+ * @brief Returns the type of next element in buffer.
+ *
+ * @buf An tst_json buffer.
+ * @return A type of next element in the buffer.
+ */
+enum tst_json_type tst_json_next_type(struct tst_json_buf *buf);
+
+/*
+ * @brief Returns if first element in JSON is object or array.
+ *
+ * @buf An tst_json buffer.
+ * @return On success returns TST_JSON_OBJ or TST_JSON_ARR. On failure TST_JSON_VOID.
+ */
+enum tst_json_type tst_json_start(struct tst_json_buf *buf);
+
+/*
+ * @brief Starts parsing of an JSON object.
+ *
+ * @buf An tst_json buffer.
+ * @res An tst_json result.
+ */
+int tst_json_obj_first(struct tst_json_buf *buf, struct tst_json_val *res);
+int tst_json_obj_next(struct tst_json_buf *buf, struct tst_json_val *res);
+
+#define TST_JSON_OBJ_FOREACH(buf, res) \
+	for (tst_json_obj_first(buf, res); tst_json_valid(res); tst_json_obj_next(buf, res))
+
+/*
+ * @brief Skips parsing of an JSON object.
+ *
+ * @buf An tst_json buffer.
+ * @return Zero on success, non-zero otherwise.
+ */
+int tst_json_obj_skip(struct tst_json_buf *buf);
+
+int tst_json_arr_first(struct tst_json_buf *buf, struct tst_json_val *res);
+int tst_json_arr_next(struct tst_json_buf *buf, struct tst_json_val *res);
+
+#define TST_JSON_ARR_FOREACH(buf, res) \
+	for (tst_json_arr_first(buf, res); tst_json_valid(res); tst_json_arr_next(buf, res))
+
+/*
+ * @brief Skips parsing of an JSON array.
+ *
+ * @buf An tst_json buffer.
+ * @return Zero on success, non-zero otherwise.
+ */
+int tst_json_arr_skip(struct tst_json_buf *buf);
+
+/*
+ * @brief Loads a file into an tst_json buffer.
+ *
+ * @path A path to a file.
+ * @return An tst_json buffer or NULL in a case of a failure.
+ */
+struct tst_json_buf *tst_json_load(const char *path);
+
+/*
+ * @brief Frees an tst_json buffer.
+ *
+ * @buf An tst_json buffer allcated by tst_json_load() function.
+ */
+void tst_json_free(struct tst_json_buf *buf);
+
+#endif /* TST_JSON_H */
diff --git a/lib/tst_json.c b/lib/tst_json.c
new file mode 100644
index 000000000..3a4cb9d0b
--- /dev/null
+++ b/lib/tst_json.c
@@ -0,0 +1,679 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * Copyright (C) 2021 Cyril Hrubis <metan@ucw.cz>
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include "tst_json.h"
+
+static inline int buf_empty(struct tst_json_buf *buf)
+{
+	return buf->off >= buf->len;
+}
+
+static int eatws(struct tst_json_buf *buf)
+{
+	while (!buf_empty(buf)) {
+		switch (buf->json[buf->off]) {
+		case ' ':
+		case '\t':
+		case '\n':
+		case '\f':
+		break;
+		default:
+			goto ret;
+		}
+
+		buf->off += 1;
+	}
+ret:
+	return buf_empty(buf);
+}
+
+static char getb(struct tst_json_buf *buf)
+{
+	if (buf_empty(buf))
+		return 0;
+
+	return buf->json[buf->off++];
+}
+
+static char peekb(struct tst_json_buf *buf)
+{
+	if (buf_empty(buf))
+		return 0;
+
+	return buf->json[buf->off];
+}
+
+static int eatb(struct tst_json_buf *buf, char ch)
+{
+	if (peekb(buf) != ch)
+		return 0;
+
+	getb(buf);
+	return 1;
+}
+
+static int hex2val(unsigned char b)
+{
+	switch (b) {
+	case '0' ... '9':
+		return b - '0';
+	case 'a' ... 'f':
+		return b - 'a' + 10;
+	case 'A' ... 'F':
+		return b - 'A' + 10;
+	default:
+		return -1;
+	}
+}
+
+static int32_t parse_ucode_cp(struct tst_json_buf *buf)
+{
+	int ret = 0, v, i;
+
+	for (i = 0; i < 4; i++) {
+		if ((v = hex2val(getb(buf))) < 0)
+			goto err;
+		ret *= 16;
+		ret += v;
+	}
+
+	return ret;
+err:
+	tst_json_err(buf, "Expected four hexadecimal digits");
+	return -1;
+}
+
+static unsigned int utf8_bytes(uint32_t ucode_cp)
+{
+	if (ucode_cp < 0x0080)
+		return 1;
+
+	if (ucode_cp < 0x0800)
+		return 2;
+
+	if (ucode_cp < 0x10000)
+		return 3;
+
+	return 4;
+}
+
+static int to_utf8(uint32_t ucode_cp, char *buf)
+{
+	if (ucode_cp < 0x0080) {
+		buf[0] = ucode_cp & 0x00f7;
+		return 1;
+	}
+
+	if (ucode_cp < 0x0800) {
+		buf[0] = 0xc0 | (0x1f & (ucode_cp>>6));
+		buf[1] = 0x80 | (0x3f & ucode_cp);
+		return 2;
+	}
+
+	if (ucode_cp < 0x10000) {
+		buf[0] = 0xe0 | (0x0f & (ucode_cp>>12));
+		buf[1] = 0x80 | (0x3f & (ucode_cp>>6));
+		buf[2] = 0x80 | (0x3f & ucode_cp);
+		return 3;
+	}
+
+	buf[0] = 0xf0 | (0x07 & (ucode_cp>>18));
+	buf[1] = 0x80 | (0x3f & (ucode_cp>>12));
+	buf[2] = 0x80 | (0x3f & (ucode_cp>>6));
+	buf[3] = 0x80 | (0x3f & ucode_cp);
+	return 4;
+}
+
+static unsigned int parse_ucode_esc(struct tst_json_buf *buf, char *str,
+                                    size_t off, size_t len)
+{
+	int32_t ucode = parse_ucode_cp(buf);
+
+	if (ucode < 0)
+		return 0;
+
+	if (!str)
+		return ucode;
+
+	if (utf8_bytes(ucode) + 1 >= len - off) {
+		tst_json_err(buf, "String buffer too short!");
+		return 0;
+	}
+
+	return to_utf8(ucode, str+off);
+}
+
+static int copy_str(struct tst_json_buf *buf, char *str, size_t len)
+{
+	size_t pos = 0;
+	int esc = 0;
+	unsigned int l;
+
+	eatb(buf, '"');
+
+	for (;;) {
+		if (buf_empty(buf)) {
+			tst_json_err(buf, "Unterminated string");
+			return 1;
+		}
+
+		if (!esc && eatb(buf, '"')) {
+			if (str)
+				str[pos] = 0;
+			return 0;
+		}
+
+		char b = getb(buf);
+
+		if (!esc && b == '\\') {
+			esc = 1;
+			continue;
+		}
+
+		if (esc) {
+			switch (b) {
+			case '"':
+			case '\\':
+			case '/':
+			break;
+			case 'b':
+				b = '\b';
+			break;
+			case 'f':
+				b = '\f';
+			break;
+			case 'n':
+				b = '\n';
+			break;
+			case 'r':
+				b = '\r';
+			break;
+			case 't':
+				b = '\t';
+			break;
+			case 'u':
+				if (!(l = parse_ucode_esc(buf, str, pos, len)))
+					return 1;
+				pos += l;
+				b = 0;
+			break;
+			default:
+				tst_json_err(buf, "Invalid escape \\%c", b);
+				return 1;
+			}
+			esc = 0;
+		}
+
+		if (str && b) {
+			if (pos + 1 >= len) {
+				tst_json_err(buf, "String buffer too short!");
+				return 1;
+			}
+
+			str[pos++] = b;
+		}
+	}
+
+	return 1;
+}
+
+static int copy_id_str(struct tst_json_buf *buf, char *str, size_t len)
+{
+	size_t pos = 0;
+
+	if (eatws(buf))
+		goto err0;
+
+	if (!eatb(buf, '"'))
+		goto err0;
+
+	for (;;) {
+		if (buf_empty(buf)) {
+			tst_json_err(buf, "Unterminated ID string");
+			return 1;
+		}
+
+		if (eatb(buf, '"')) {
+			str[pos] = 0;
+			break;
+		}
+
+		if (pos >= len-1) {
+			tst_json_err(buf, "ID string too long");
+			return 1;
+		}
+
+		str[pos++] = getb(buf);
+	}
+
+	if (eatws(buf))
+		goto err1;
+
+	if (!eatb(buf, ':'))
+		goto err1;
+
+	return 0;
+err0:
+	tst_json_err(buf, "Expected ID string");
+	return 1;
+err1:
+	tst_json_err(buf, "Expected ':' after ID string");
+	return 1;
+}
+
+static int is_digit(char b)
+{
+	switch (b) {
+	case '0' ... '9':
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+static int get_number(struct tst_json_buf *buf, struct tst_json_val *res)
+{
+	long val = 0;
+	int neg = 0;
+
+	if (peekb(buf) == '-') {
+		neg = 1;
+		getb(buf);
+		if (!is_digit(peekb(buf))) {
+			tst_json_err(buf, "Expected digits after '-'");
+			return 1;
+		}
+	}
+
+	while (is_digit(peekb(buf))) {
+		val *= 10;
+		val += getb(buf) - '0';
+		//TODO: overflow?
+	}
+
+	if (neg)
+		val = -val;
+
+	res->val_int = val;
+
+	return 0;
+}
+
+int tst_json_obj_skip(struct tst_json_buf *buf)
+{
+	struct tst_json_val res = {};
+
+	TST_JSON_OBJ_FOREACH(buf, &res) {
+		switch (res.type) {
+		case TST_JSON_OBJ:
+			if (tst_json_obj_skip(buf))
+				return 1;
+		break;
+		case TST_JSON_ARR:
+			if (tst_json_arr_skip(buf))
+				return 1;
+		break;
+		default:
+		break;
+		}
+	}
+
+	return 0;
+}
+
+int tst_json_arr_skip(struct tst_json_buf *buf)
+{
+	struct tst_json_val res = {};
+
+	TST_JSON_ARR_FOREACH(buf, &res) {
+		switch (res.type) {
+		case TST_JSON_OBJ:
+			if (tst_json_obj_skip(buf))
+				return 1;
+		break;
+		case TST_JSON_ARR:
+			if (tst_json_arr_skip(buf))
+				return 1;
+		break;
+		default:
+		break;
+		}
+	}
+
+	return 0;
+}
+
+enum tst_json_type tst_json_next_type(struct tst_json_buf *buf)
+{
+	if (eatws(buf)) {
+		tst_json_err(buf, "Unexpected end");
+		return TST_JSON_VOID;
+	}
+
+	char b = peekb(buf);
+
+	switch (b) {
+	case '{':
+		return TST_JSON_OBJ;
+	case '[':
+		return TST_JSON_ARR;
+	case '"':
+		return TST_JSON_STR;
+	case '-':
+	case '0' ... '9':
+		return TST_JSON_INT;
+	default:
+		tst_json_err(buf, "Expected object, array, number or string");
+		return TST_JSON_VOID;
+	}
+}
+
+enum tst_json_type tst_json_start(struct tst_json_buf *buf)
+{
+	enum tst_json_type type = tst_json_next_type(buf);
+
+	switch (type) {
+	case TST_JSON_ARR:
+	case TST_JSON_OBJ:
+	case TST_JSON_VOID:
+	break;
+	case TST_JSON_INT:
+	case TST_JSON_STR:
+		tst_json_err(buf, "JSON can start only with array or object");
+		type = TST_JSON_VOID;
+	break;
+	}
+
+	return type;
+}
+
+static int get_value(struct tst_json_buf *buf, struct tst_json_val *res)
+{
+	res->type = tst_json_next_type(buf);
+
+	switch (res->type) {
+	case TST_JSON_STR:
+		if (copy_str(buf, res->buf, res->buf_size)) {
+			res->type = TST_JSON_VOID;
+			return 0;
+		}
+		res->val_str = res->buf;
+		return 1;
+	case TST_JSON_INT:
+		return !get_number(buf, res);
+	case TST_JSON_VOID:
+		//tst_json_err(buf, "Unexpected character");
+		return 0;
+	case TST_JSON_ARR:
+	case TST_JSON_OBJ:
+		buf->sub_off = buf->off;
+		return 1;
+	}
+
+	return 1;
+}
+
+static int pre_next(struct tst_json_buf *buf, struct tst_json_val *res)
+{
+	if (!eatb(buf, ',')) {
+		tst_json_err(buf, "Expected ','");
+		res->type = TST_JSON_VOID;
+		return 1;
+	}
+
+	if (eatws(buf)) {
+		tst_json_err(buf, "Unexpected end");
+		res->type = TST_JSON_VOID;
+		return 1;
+	}
+
+	return 0;
+}
+
+static int check_end(struct tst_json_buf *buf, struct tst_json_val *res, char b)
+{
+	if (eatws(buf)) {
+		tst_json_err(buf, "Unexpected end");
+		return 1;
+	}
+
+	if (eatb(buf, b)) {
+		res->type = TST_JSON_VOID;
+		return 1;
+	}
+
+	return 0;
+}
+
+static int obj_next(struct tst_json_buf *buf, struct tst_json_val *res)
+{
+	if (copy_id_str(buf, res->id, sizeof(res->id)))
+		return 0;
+
+	return get_value(buf, res);
+}
+
+static int check_err(struct tst_json_buf *buf, struct tst_json_val *res)
+{
+	if (tst_json_is_err(buf)) {
+		res->type = TST_JSON_VOID;
+		return 1;
+	}
+
+	return 0;
+}
+
+int tst_json_obj_next(struct tst_json_buf *buf, struct tst_json_val *res)
+{
+	if (check_err(buf, res))
+		return 0;
+
+	if (check_end(buf, res, '}'))
+		return 0;
+
+	if (pre_next(buf, res))
+		return 0;
+
+	return obj_next(buf, res);
+}
+
+static int any_first(struct tst_json_buf *buf, char b)
+{
+	if (eatws(buf)) {
+		tst_json_err(buf, "Unexpected end");
+		return 1;
+	}
+
+	if (!eatb(buf, b)) {
+		tst_json_err(buf, "Expected '%c'", b);
+		return 1;
+	}
+
+	return 0;
+}
+
+int tst_json_obj_first(struct tst_json_buf *buf, struct tst_json_val *res)
+{
+	if (check_err(buf, res))
+		return 0;
+
+	if (any_first(buf, '{'))
+		return 1;
+
+	if (check_end(buf, res, '}'))
+		return 0;
+
+	return obj_next(buf, res);
+}
+
+static int arr_next(struct tst_json_buf *buf, struct tst_json_val *res)
+{
+	return get_value(buf, res);
+}
+
+int tst_json_arr_first(struct tst_json_buf *buf, struct tst_json_val *res)
+{
+	if (check_err(buf, res))
+		return 0;
+
+	if (any_first(buf, '['))
+		return 1;
+
+	if (check_end(buf, res, ']'))
+		return 0;
+
+	return arr_next(buf, res);
+}
+
+int tst_json_arr_next(struct tst_json_buf *buf, struct tst_json_val *res)
+{
+	if (check_err(buf, res))
+		return 0;
+
+	if (check_end(buf, res, ']'))
+		return 0;
+
+	if (pre_next(buf, res))
+		return 0;
+
+	return arr_next(buf, res);
+}
+
+void tst_json_err(struct tst_json_buf *buf, const char *fmt, ...)
+{
+	va_list va;
+
+	va_start(va, fmt);
+	vsnprintf(buf->err, TST_JSON_ERR_MAX, fmt, va);
+	va_end(va);
+}
+
+static void print_line(FILE *f, const char *line)
+{
+	while (*line && *line != '\n')
+		fputc(*(line++), f);
+}
+
+static void print_spaces(FILE *f, size_t count)
+{
+	while (count--)
+		fputc(' ', f);
+}
+
+static void print_spaceline(FILE *f, const char *line, size_t count)
+{
+	size_t i;
+
+	for (i = 0; i < count; i++)
+		fputc(line[i] == '\t' ? '\t' : ' ', f);
+}
+
+#define ERR_LINES 10
+
+#define MIN(A, B) ((A < B) ? (A) : (B))
+
+void tst_json_err_print(FILE *f, struct tst_json_buf *buf)
+{
+	ssize_t i;
+	const char *lines[ERR_LINES] = {};
+	size_t cur_line = 0;
+	size_t cur_off = 0;
+	size_t last_off = buf->off;
+
+	for (;;) {
+		lines[(cur_line++) % ERR_LINES] = buf->json + cur_off;
+
+		while (cur_off < buf->len && buf->json[cur_off] != '\n')
+			cur_off++;
+
+		if (cur_off >= buf->off)
+			break;
+
+		cur_off++;
+		last_off = buf->off - cur_off;
+	}
+
+	fprintf(f, "Parse error at line %zu\n\n", cur_line);
+
+	size_t idx = 0;
+
+	for (i = MIN(ERR_LINES, cur_line); i > 0; i--) {
+		idx = (cur_line - i) % ERR_LINES;
+		fprintf(f, "%03zu: ", cur_line - i + 1);
+		print_line(f, lines[idx]);
+		fputc('\n', f);
+	}
+
+	print_spaces(f, 5);
+	print_spaceline(f, lines[idx], last_off);
+	fprintf(f, "^\n");
+	fprintf(f, "%s\n", buf->err);
+}
+
+struct tst_json_buf *tst_json_load(const char *path)
+{
+	int fd = open(path, O_RDONLY);
+	struct tst_json_buf *ret;
+	ssize_t res;
+	off_t len, off = 0;
+
+	if (fd < 0)
+		return NULL;
+
+	len = lseek(fd, 0, SEEK_END);
+	if (len == (off_t)-1) {
+		fprintf(stderr, "lseek() failed\n");
+		goto err0;
+	}
+
+	if (lseek(fd, 0, SEEK_SET) == (off_t)-1) {
+		fprintf(stderr, "lseek() failed\n");
+		goto err0;
+	}
+
+	ret = malloc(sizeof(struct tst_json_buf) + len + 1);
+	if (!ret) {
+		fprintf(stderr, "malloc() failed\n");
+		goto err0;
+	}
+
+	memset(ret, 0, sizeof(*ret));
+
+	ret->buf[len] = 0;
+	ret->len = len;
+	ret->json = ret->buf;
+
+	while (off < len) {
+		res = read(fd, ret->buf + off, len - off);
+		if (res < 0) {
+			fprintf(stderr, "read() failed\n");
+			goto err1;
+		}
+
+		off += res;
+	}
+
+	close(fd);
+
+	return ret;
+err1:
+	free(ret);
+err0:
+	close(fd);
+	return NULL;
+}
+
+void tst_json_free(struct tst_json_buf *buf)
+{
+	free(buf);
+}
-- 
2.26.2


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [LTP] [PATCH v2 3/5] lib: Add hardware discovery code
  2021-02-24 16:50 [LTP] [PATCH v2 0/5] Another attempt at hardware discovery Cyril Hrubis
  2021-02-24 16:50 ` [LTP] [PATCH v2 1/5] lib: tst_cmd: Make tst_cmd() usable for global paths Cyril Hrubis
  2021-02-24 16:50 ` [LTP] [PATCH v2 2/5] lib: Add minimalistic json parser Cyril Hrubis
@ 2021-02-24 16:50 ` Cyril Hrubis
  2021-02-24 16:50 ` [LTP] [PATCH v2 4/5] Sample hardware discovery and reconfigure scripts Cyril Hrubis
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 15+ messages in thread
From: Cyril Hrubis @ 2021-02-24 16:50 UTC (permalink / raw)
  To: ltp

See lib/tst_hardware.h for details.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 include/tst_hwconf.h |  13 +++
 include/tst_test.h   |   3 +
 lib/tst_hardware.c   | 218 +++++++++++++++++++++++++++++++++++++++++++
 lib/tst_hardware.h   |  83 ++++++++++++++++
 lib/tst_test.c       |  30 ++++++
 5 files changed, 347 insertions(+)
 create mode 100644 include/tst_hwconf.h
 create mode 100644 lib/tst_hardware.c
 create mode 100644 lib/tst_hardware.h

diff --git a/include/tst_hwconf.h b/include/tst_hwconf.h
new file mode 100644
index 000000000..5b0b95441
--- /dev/null
+++ b/include/tst_hwconf.h
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2020-2021 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+#ifndef TST_HWCONF_H__
+#define TST_HWCONF_H__
+
+#include "tst_json.h"
+
+struct tst_json_buf *tst_hwconf_get(void);
+
+#endif	/* TST_HWCONF_H__ */
diff --git a/include/tst_test.h b/include/tst_test.h
index 1fbebe752..bd6de601e 100644
--- a/include/tst_test.h
+++ b/include/tst_test.h
@@ -230,6 +230,9 @@ struct tst_test {
 	/* NULL terminated array of needed kernel drivers */
 	const char * const *needs_drivers;
 
+	/* Unique hardware identifier */
+	const char *needs_hardware;
+
 	/*
 	 * NULL terminated array of (/proc, /sys) files to save
 	 * before setup and restore after cleanup
diff --git a/lib/tst_hardware.c b/lib/tst_hardware.c
new file mode 100644
index 000000000..c67c9b5c6
--- /dev/null
+++ b/lib/tst_hardware.c
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2020-2021 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "tst_hardware.h"
+#include "tst_hwconf.h"
+
+#define TST_NO_DEFAULT_MAIN
+#include "tst_test.h"
+
+static struct tst_hwconf *cur_conf;
+static char *reconfigure_path;
+static struct tst_hwconf *conf_root;
+static unsigned int conf_cnt;
+
+static int new_hwconf(char *buf, size_t start_off, size_t end_off)
+{
+	struct tst_hwconf *conf = malloc(sizeof(struct tst_hwconf));
+
+	if (!conf) {
+		tst_res(TWARN, "malloc() failed!");
+		return 0;
+	}
+
+	memset(conf, 0, sizeof(*conf));
+
+	conf->json.json = buf + start_off;
+	conf->json.len = end_off - start_off;
+
+	struct tst_json_val val = {
+		.buf = conf->uid,
+		.buf_size = sizeof(conf->uid)
+	};
+
+	TST_JSON_OBJ_FOREACH(&conf->json, &val) {
+		switch (val.type) {
+		case TST_JSON_STR:
+			if (!strcmp(val.id, "uid"))
+				goto done;
+		break;
+		case TST_JSON_ARR:
+			tst_json_arr_skip(&conf->json);
+		break;
+		case TST_JSON_OBJ:
+			tst_json_obj_skip(&conf->json);
+		break;
+		default:
+		break;
+		}
+	}
+
+	free(conf);
+
+	return 1;
+done:
+	tst_json_reset(&conf->json);
+
+	if (!cur_conf)
+		conf_root = conf;
+	else
+		cur_conf->next = conf;
+
+	cur_conf = conf;
+	conf_cnt++;
+
+	tst_res(TINFO, "Added hardware configuration UID='%s'", conf->uid);
+
+	return 0;
+}
+
+static void parse_devices(struct tst_json_buf *json)
+{
+	struct tst_json_val val = {};
+	size_t start_off;
+
+	TST_JSON_ARR_FOREACH(json, &val) {
+		switch (val.type) {
+		case TST_JSON_OBJ:
+			start_off = json->sub_off;
+			tst_json_obj_skip(json);
+			if (new_hwconf(json->buf, start_off, json->off))
+				tst_json_err(json, "Missing 'uid' in hwconf entry!");
+		break;
+		case TST_JSON_ARR:
+		case TST_JSON_INT:
+		case TST_JSON_STR:
+			tst_json_err(json, "Invalid record in hwconf list!");
+		break;
+		case TST_JSON_VOID:
+		break;
+		}
+	}
+}
+
+static void save_reconfigure(const char *reconfigure, const char *ltproot)
+{
+	if (reconfigure[0] == '/') {
+		reconfigure_path = strdup(reconfigure);
+		if (!reconfigure_path)
+			tst_brk(TBROK, "strdup() failed");
+	} else {
+		if (asprintf(&reconfigure_path, "%s/%s", ltproot, reconfigure) < 0)
+			tst_brk(TBROK, "asprintf() failed");
+	}
+}
+
+unsigned int tst_hwlist_cnt(void)
+{
+	return conf_cnt;
+}
+
+static void reconfigure(void)
+{
+	if (!reconfigure_path)
+		return;
+
+	if (!cur_conf)
+		return;
+
+	const char *const argv[] = {reconfigure_path, cur_conf->uid, NULL};
+
+	tst_res(TINFO, "Running reconfigure '%s %s'", reconfigure_path, cur_conf->uid);
+
+	tst_cmd(argv, NULL, NULL, TST_CMD_TCONF_ON_MISSING);
+}
+
+void tst_hwlist_reset(void)
+{
+	cur_conf = conf_root;
+
+	reconfigure();
+}
+
+void tst_hwlist_next(void)
+{
+	cur_conf = cur_conf->next;
+
+	reconfigure();
+}
+
+struct tst_json_buf *tst_hwconf_get(void)
+{
+	if (!cur_conf)
+		return NULL;
+
+	return &cur_conf->json;
+}
+
+unsigned int tst_hwlist_discover(const char *hardware_uid)
+{
+	const char *ltproot = getenv("LTPROOT");
+	const char *hardware_discovery = getenv("HARDWARE_DISCOVERY");
+	char buf[2048];
+
+	if (!hardware_discovery) {
+		if (!ltproot) {
+			tst_res(TCONF, "No LTPROOT nor HARDWARE_DISCOVERY set!");
+			return 0;
+		}
+
+		snprintf(buf, sizeof(buf), "%s/hardware-discovery.sh", ltproot);
+
+		hardware_discovery = buf;
+	}
+
+	const char *const argv[] = {hardware_discovery, hardware_uid, NULL};
+
+	tst_res(TINFO, "Executing '%s %s'", hardware_discovery, hardware_uid);
+
+	//TODO: read the data from a pipe instead
+	unlink("/tmp/hwlist.json");
+	tst_cmd(argv, "/tmp/hwlist.json", NULL, TST_CMD_TCONF_ON_MISSING);
+
+	struct tst_json_buf *json = tst_json_load("/tmp/hwlist.json");
+
+	if (!json)
+		tst_brk(TBROK, "Failed to load JSON hardware description");
+
+	if (tst_json_start(json) != TST_JSON_OBJ)
+		tst_brk(TBROK, "JSON hardware description must start with object!");
+
+	struct tst_json_val val = {.buf = buf, .buf_size = sizeof(buf)};
+
+	TST_JSON_OBJ_FOREACH(json, &val) {
+		switch (val.type) {
+		case TST_JSON_STR:
+			if (!strcmp(val.id, "reconfigure"))
+				save_reconfigure(val.val_str, ltproot);
+			else
+				tst_json_err(json, "Invalid object attr id='%s'", val.id);
+		break;
+		case TST_JSON_INT:
+		case TST_JSON_OBJ:
+			tst_json_err(json, "Invalid object attr id='%s'", val.id);
+		break;
+		case TST_JSON_ARR:
+			if (!strcmp(val.id, "hwconfs"))
+				parse_devices(json);
+			else
+				tst_json_err(json, "Invalid array attr id='%s'", val.id);
+		break;
+		case TST_JSON_VOID:
+		break;
+		}
+	}
+
+	if (tst_json_is_err(json)) {
+		tst_json_err_print(stderr, json);
+		tst_brk(TBROK, "Failed to parse JSON hardware description");
+	}
+
+	return conf_cnt;
+}
diff --git a/lib/tst_hardware.h b/lib/tst_hardware.h
new file mode 100644
index 000000000..9abf5d648
--- /dev/null
+++ b/lib/tst_hardware.h
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2020-2021 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*
+ * Hardware discovery code.
+ *
+ * Each hardware type is uniquely identified by an hardware unique id. Test
+ * that needs a particular hardware sets the .needs_hardware field in the
+ * tst_test structure which is then passed to the discovery code.
+ *
+ * The discovery launches a script/binary that returns a JSON in a format:
+ *
+ * {
+ *  "reconfigure": "/path/to/reconfigure/script",
+ *  "hwconfs": [
+ *   {
+ *     "uid": "device_unique_id01",
+ *     ...
+ *   },
+ *   {
+ *    "uid": "device_unique_id02",
+ *    ...
+ *   }
+ *  ]
+ * }
+ *
+ * The optional reconfigure script could be used to set up the hardware, e.g.
+ * connect different serial ports together with relays, the only parameter
+ * it takes is the device uid. E.g. to set up the testing environment for the
+ * first device in the list above LTP will execute command:
+ * '/path/to/reconfigure/script "device_unique_id01"' etc.
+ *
+ * The second parameter in the JSON is "hwconfs" array that describes all
+ * available hardware configurations. These objects, apart from "uid" are not
+ * interpreted by the test libary but rather passed down to the test one object
+ * per test iteration.
+ */
+
+#ifndef TST_HARDWARE_H__
+#define TST_HARDWARE_H__
+
+#include "tst_json.h"
+
+struct tst_hwconf {
+	struct tst_hwconf *next;
+	/* unique id for the hardware configuration */
+	char uid[128];
+	/* buffer with the JSON description */
+	struct tst_json_buf json;
+};
+
+/*
+ * Discovers a hardware based on hardware_uid.
+ *
+ * Exits the test with TBROK on error and with TCONF if no hardware was discovered.
+ */
+unsigned int tst_hwlist_discover(const char *hardware_uid);
+
+/*
+ * Returns number of parsed entries.
+ */
+unsigned int tst_hwlist_cnt(void);
+
+/*
+ * Resets current tst_hwconf to point to the first hardware entry.
+ */
+void tst_hwlist_reset(void);
+
+/*
+ * Allows to loop over all entries in the hareware list.
+ *
+ * If needed calls the reconfigure script.
+ */
+void tst_hwlist_next(void);
+
+/*
+ * Free the hwlist.
+ */
+void tst_hwlist_free(void);
+
+#endif /* TST_HARDWARE_H__ */
diff --git a/lib/tst_test.c b/lib/tst_test.c
index 6bbee030b..3883ce227 100644
--- a/lib/tst_test.c
+++ b/lib/tst_test.c
@@ -27,6 +27,7 @@
 #include "tst_wallclock.h"
 #include "tst_sys_conf.h"
 #include "tst_kconfig.h"
+#include "tst_hardware.h"
 
 #include "old_resource.h"
 #include "old_device.h"
@@ -499,6 +500,14 @@ static void print_test_tags(void)
 	printf("\n");
 }
 
+static void print_test_hardware(void)
+{
+	if (!tst_test->needs_hardware)
+		return;
+
+	printf("\nNeeded hardware\n--------------\n%s\n\n", tst_test->needs_hardware);
+}
+
 static void check_option_collision(void)
 {
 	unsigned int i, j;
@@ -578,6 +587,7 @@ static void parse_opts(int argc, char *argv[])
 		case 'h':
 			print_help();
 			print_test_tags();
+			print_test_hardware();
 			exit(0);
 		case 'i':
 			iterations = atoi(optarg);
@@ -942,6 +952,18 @@ static void do_setup(int argc, char *argv[])
 				tst_brk(TCONF, "%s driver not available", name);
 	}
 
+	if (tst_test->needs_hardware) {
+		unsigned int cnt = tst_hwlist_discover(tst_test->needs_hardware);
+
+		if (!cnt) {
+			tst_brk(TCONF, "No hardware '%s' discovered",
+				tst_test->needs_hardware);
+		}
+
+		tst_res(TINFO, "Found %u hardware configurations for '%s'",
+			cnt, tst_test->needs_hardware);
+	}
+
 	if (tst_test->format_device)
 		tst_test->needs_device = 1;
 
@@ -1395,6 +1417,11 @@ void tst_run_tcases(int argc, char *argv[], struct tst_test *self)
 	if (tst_test->test_variants)
 		test_variants = tst_test->test_variants;
 
+	if (tst_test->needs_hardware) {
+		test_variants = tst_hwlist_cnt();
+		tst_hwlist_reset();
+	}
+
 	for (tst_variant = 0; tst_variant < test_variants; tst_variant++) {
 		if (tst_test->all_filesystems)
 			ret |= run_tcases_per_fs();
@@ -1403,6 +1430,9 @@ void tst_run_tcases(int argc, char *argv[], struct tst_test *self)
 
 		if (ret & ~(TCONF))
 			goto exit;
+
+		if (tst_test->needs_hardware)
+			tst_hwlist_next();
 	}
 
 exit:
-- 
2.26.2


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [LTP] [PATCH v2 4/5] Sample hardware discovery and reconfigure scripts
  2021-02-24 16:50 [LTP] [PATCH v2 0/5] Another attempt at hardware discovery Cyril Hrubis
                   ` (2 preceding siblings ...)
  2021-02-24 16:50 ` [LTP] [PATCH v2 3/5] lib: Add hardware discovery code Cyril Hrubis
@ 2021-02-24 16:50 ` Cyril Hrubis
  2021-02-24 16:50 ` [LTP] [PATCH v2 5/5] testcases: uart01: Add Cyril Hrubis
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 15+ messages in thread
From: Cyril Hrubis @ 2021-02-24 16:50 UTC (permalink / raw)
  To: ltp

This is not going to be part of the final patchset.

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 hardware-discovery.sh   | 36 ++++++++++++++++++++++++++++++++++++
 hardware-reconfigure.sh |  3 +++
 2 files changed, 39 insertions(+)
 create mode 100755 hardware-discovery.sh
 create mode 100755 hardware-reconfigure.sh

diff --git a/hardware-discovery.sh b/hardware-discovery.sh
new file mode 100755
index 000000000..2d1eeefaa
--- /dev/null
+++ b/hardware-discovery.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+if [ "$1" = "UART-loopback" ]; then
+	echo '{'
+	echo ' "reconfigure": "hardware-reconfigure.sh",'
+	echo ' "hwconfs": ['
+	echo '  {'
+	echo '   "uid": "ttyUSB0-ttyUSB0-01",'
+	echo '   "tx": "/dev/ttyUSB0",'
+	echo '   "rx": "/dev/ttyUSB0",'
+	echo '   "hwflow": 0,'
+	echo '   "baud_rates": ['
+	echo '    4800,'
+	echo '    9600,'
+	echo '    19200'
+	echo '   ]'
+	echo '  },'
+	echo '  {'
+	echo '   "uid": "ttyUSB0-ttyUSB0-02",'
+	echo '   "tx": "/dev/ttyUSB0",'
+	echo '   "rx": "/dev/ttyUSB0",'
+	echo '   "hwflow": 1,'
+	echo '   "baud_rates": ['
+	echo '    4800,'
+	echo '    9600,'
+	echo '    19200'
+	echo '   ]'
+	echo '  }'
+	echo ' ]'
+	echo '}'
+
+	exit 0
+fi
+
+echo '{}'
+exit 0
diff --git a/hardware-reconfigure.sh b/hardware-reconfigure.sh
new file mode 100755
index 000000000..079f6588a
--- /dev/null
+++ b/hardware-reconfigure.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo "Reconfigure '$@'"
-- 
2.26.2


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [LTP] [PATCH v2 5/5] testcases: uart01: Add.
  2021-02-24 16:50 [LTP] [PATCH v2 0/5] Another attempt at hardware discovery Cyril Hrubis
                   ` (3 preceding siblings ...)
  2021-02-24 16:50 ` [LTP] [PATCH v2 4/5] Sample hardware discovery and reconfigure scripts Cyril Hrubis
@ 2021-02-24 16:50 ` Cyril Hrubis
  2021-02-26 19:20 ` [LTP] [Automated-testing] [PATCH v2 0/5] Another attempt at hardware discovery Tim.Bird
  2021-03-01 10:30 ` [LTP] " Cixi Geng
  6 siblings, 0 replies; 15+ messages in thread
From: Cyril Hrubis @ 2021-02-24 16:50 UTC (permalink / raw)
  To: ltp

Execute from the git checkout as:

 LTPROOT=~/foo/bar/ltp/ HARDWARE_DISCOVERY=${LTPROOT}hardware-discovery.sh ./uart01 -s 10240

Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
 runtest/device_drivers                        |   2 +
 testcases/kernel/device-drivers/Makefile      |   1 +
 .../kernel/device-drivers/uart/.gitignore     |   1 +
 testcases/kernel/device-drivers/uart/Makefile |   3 +
 testcases/kernel/device-drivers/uart/uart01.c | 620 ++++++++++++++++++
 5 files changed, 627 insertions(+)
 create mode 100644 runtest/device_drivers
 create mode 100644 testcases/kernel/device-drivers/uart/.gitignore
 create mode 100644 testcases/kernel/device-drivers/uart/Makefile
 create mode 100644 testcases/kernel/device-drivers/uart/uart01.c

diff --git a/runtest/device_drivers b/runtest/device_drivers
new file mode 100644
index 000000000..9295c7a90
--- /dev/null
+++ b/runtest/device_drivers
@@ -0,0 +1,2 @@
+uart01
+uart01 -s 10240
diff --git a/testcases/kernel/device-drivers/Makefile b/testcases/kernel/device-drivers/Makefile
index 55e0d25a0..a214f211b 100644
--- a/testcases/kernel/device-drivers/Makefile
+++ b/testcases/kernel/device-drivers/Makefile
@@ -27,6 +27,7 @@ SUBDIRS		:= acpi \
 		   rtc \
 		   tbio \
 		   uaccess \
+		   uart \
 		   zram
 
 include $(top_srcdir)/include/mk/generic_trunk_target.mk
diff --git a/testcases/kernel/device-drivers/uart/.gitignore b/testcases/kernel/device-drivers/uart/.gitignore
new file mode 100644
index 000000000..9333e8db9
--- /dev/null
+++ b/testcases/kernel/device-drivers/uart/.gitignore
@@ -0,0 +1 @@
+uart01
diff --git a/testcases/kernel/device-drivers/uart/Makefile b/testcases/kernel/device-drivers/uart/Makefile
new file mode 100644
index 000000000..939e433d0
--- /dev/null
+++ b/testcases/kernel/device-drivers/uart/Makefile
@@ -0,0 +1,3 @@
+top_srcdir	?= ../../../..
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/device-drivers/uart/uart01.c b/testcases/kernel/device-drivers/uart/uart01.c
new file mode 100644
index 000000000..3f244fe8f
--- /dev/null
+++ b/testcases/kernel/device-drivers/uart/uart01.c
@@ -0,0 +1,620 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+   Copyright (c) 2014 Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+   Copyright (c) 2020-2021 Cyril Hrubis <chrubis@suse.cz>
+
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <stdint.h>
+#include <poll.h>
+#include <sys/ioctl.h>
+#include <linux/serial.h>
+
+#include "tst_test.h"
+#include "tst_hwconf.h"
+
+static const char hex_asc[] = "0123456789abcdef";
+#define hex_asc_lo(x)	hex_asc[((x) & 0x0f)]
+#define hex_asc_hi(x)	hex_asc[((x) & 0xf0) >> 4]
+
+static struct uart_params {
+	int hwflow;
+	char rx[128];
+	char tx[128];
+	unsigned int baud_rates[20];
+} uart_params = {
+	.baud_rates = {9600},
+};
+
+struct g_opt {
+	char *uart_dev;
+	char *file_trans;
+	int baud_rate;
+	unsigned int loops;
+	unsigned char hwflow;
+	unsigned char do_termios;
+#define MODE_TX_ONLY    (1 << 0)
+#define MODE_RX_ONLY    (1 << 1)
+#define MODE_DUPLEX     (MODE_TX_ONLY | MODE_RX_ONLY)
+	unsigned int mode;
+	unsigned char *cmp_buff;
+};
+
+static int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
+{
+	int i;
+
+	i = vsnprintf(buf, size, fmt, args);
+
+	if (i < (int)size)
+		return i;
+	if (size != 0)
+		return size - 1;
+	return 0;
+}
+
+static int scnprintf(char *buf, size_t size, const char *fmt, ...)
+{
+	va_list args;
+	int i;
+
+	va_start(args, fmt);
+	i = vscnprintf(buf, size, fmt, args);
+	va_end(args);
+
+	return i;
+}
+
+
+static void hex_dump_to_buffer(const void *buf, int len, int rowsize,
+		int groupsize, char *linebuf, int linebuflen, int ascii)
+{
+	const uint8_t *ptr = buf;
+	uint8_t ch;
+	int j, lx = 0;
+	int ascii_column;
+
+	if (rowsize != 16 && rowsize != 32)
+		rowsize = 16;
+
+	if (!len)
+		goto nil;
+	if (len > rowsize)      /* limit to one line at a time */
+		len = rowsize;
+	if ((len % groupsize) != 0)     /* no mixed size output */
+		groupsize = 1;
+
+	switch (groupsize) {
+	case 8: {
+		const uint64_t *ptr8 = buf;
+		int ngroups = len / groupsize;
+
+		for (j = 0; j < ngroups; j++)
+			lx += scnprintf(linebuf + lx, linebuflen - lx,
+					"%s%16.16llx", j ? " " : "",
+					(unsigned long long)*(ptr8 + j));
+		ascii_column = 17 * ngroups + 2;
+		break;
+		}
+
+	case 4: {
+		const uint32_t *ptr4 = buf;
+		int ngroups = len / groupsize;
+
+		for (j = 0; j < ngroups; j++)
+			lx += scnprintf(linebuf + lx, linebuflen - lx,
+					"%s%8.8x", j ? " " : "", *(ptr4 + j));
+		ascii_column = 9 * ngroups + 2;
+		break;
+		}
+
+	case 2: {
+		const uint16_t *ptr2 = buf;
+		int ngroups = len / groupsize;
+
+		for (j = 0; j < ngroups; j++)
+			lx += scnprintf(linebuf + lx, linebuflen - lx,
+					"%s%4.4x", j ? " " : "", *(ptr2 + j));
+		ascii_column = 5 * ngroups + 2;
+		break;
+		}
+
+	default:
+		for (j = 0; (j < len) && (lx + 3) <= linebuflen; j++) {
+			ch = ptr[j];
+			linebuf[lx++] = hex_asc_hi(ch);
+			linebuf[lx++] = hex_asc_lo(ch);
+			linebuf[lx++] = ' ';
+			if (j == 7)
+				linebuf[lx++] = ' ';
+		}
+		if (j)
+			lx--;
+
+		ascii_column = 3 * rowsize + 2 + 2;
+		break;
+	}
+	if (!ascii)
+		goto nil;
+
+	while (lx < (linebuflen - 1) && lx < (ascii_column - 1))
+		linebuf[lx++] = ' ';
+	for (j = 0; (j < len) && (lx + 2) < linebuflen; j++) {
+		ch = ptr[j];
+		linebuf[lx++] = (isascii(ch) && isprint(ch)) ? ch : '.';
+	}
+nil:
+	linebuf[lx++] = '\0';
+}
+
+static void print_hex_dump(const void *buf, int len, int offset)
+{
+	const uint8_t *ptr = buf;
+	int i, linelen, remaining = len;
+	char linebuf[32 * 3 + 2 + 32 + 2 + 1];
+	int rowsize = 16;
+	int groupsize = 1;
+
+	if (rowsize != 16 && rowsize != 32)
+		rowsize = 16;
+
+	for (i = 0; i < len; i += rowsize) {
+		linelen = MIN(remaining, rowsize);
+		remaining -= rowsize;
+
+		hex_dump_to_buffer(ptr + i, linelen, rowsize, groupsize,
+				linebuf, sizeof(linebuf), 1);
+
+		printf("%.8x: %s\n", i + offset, linebuf);
+	}
+}
+
+static int stress_test_uart_once(struct g_opt *opts, int fd, unsigned char *data,
+		off_t data_len)
+{
+	unsigned char *cmp_data = opts->cmp_buff;
+	ssize_t size;
+	int wait_rx;
+	int wait_tx;
+	ssize_t progress_rx = 0;
+	ssize_t progress_tx = 0;
+	unsigned int reads = 0;
+	unsigned int writes = 0;
+
+	do {
+		struct pollfd pfd = {
+			.fd = fd,
+		};
+		int ret;
+
+		if (opts->mode & MODE_RX_ONLY && progress_rx < data_len) {
+			pfd.events |= POLLIN;
+			wait_rx = 1;
+		} else {
+			wait_rx = 0;
+		}
+
+		if (opts->mode & MODE_TX_ONLY && progress_tx < data_len) {
+			pfd.events |= POLLOUT;
+			wait_tx = 1;
+		} else {
+			wait_tx = 0;
+		}
+
+		ret = poll(&pfd, 1, 10 * 1000);
+		if (ret == 0) {
+			tst_res(TFAIL, "timeout, RX/TX: %zd/%zd\n", progress_rx, progress_tx);
+			break;
+		}
+		if (ret < 0) {
+			tst_res(TFAIL | TERRNO, "poll() failed");
+			return 1;
+		}
+
+		if (pfd.revents & POLLIN) {
+			size = read(fd, cmp_data + progress_rx, data_len - progress_rx);
+			if (size < 0) {
+				tst_res(TFAIL | TERRNO, "read() failed");
+				return 1;
+			}
+			reads++;
+			progress_rx += size;
+			if (progress_rx >= data_len)
+				wait_rx = 0;
+		}
+
+		if (pfd.revents & POLLOUT) {
+
+			size = write(fd, data + progress_tx, data_len - progress_tx);
+			if (size < 0) {
+				tst_res(TFAIL | TERRNO, "write() failed");
+				return 1;
+			}
+			writes++;
+			progress_tx += size;
+			if (progress_tx >= data_len)
+				wait_tx = 0;
+		}
+	} while (wait_rx || wait_tx);
+
+	tst_res(TINFO, "Needed %u reads %u writes ", reads, writes);
+
+	if (opts->mode & MODE_RX_ONLY) {
+		unsigned int i;
+		int found = 0;
+		unsigned int min_pos;
+		unsigned int max_pos;
+
+		if (!memcmp(data, cmp_data, data_len)) {
+			tst_res(TPASS, "RX passed");
+			return 0;
+		}
+
+		for (i = 0; i < data_len && !found; i++) {
+			if (data[i] != cmp_data[i]) {
+				found = 1;
+				break;
+			}
+		}
+
+		if (!found) {
+			tst_res(TFAIL, "memcmp() didn't match but manual cmp did");
+			return 1;
+		}
+
+		max_pos = (i & ~0xfULL) + 16 * 3;
+		if (max_pos > data_len)
+			max_pos = data_len;
+
+		min_pos = i & ~0xfULL;
+		if (min_pos > 16 * 3)
+			min_pos -= 16 * 3;
+		else
+			min_pos = 0;
+
+		tst_res(TFAIL, "Oh oh, inconsistency at pos %d (0x%x)", i, i);
+
+		printf("\nOriginal sample:\n");
+		print_hex_dump(data + min_pos, max_pos - min_pos, min_pos);
+
+		printf("\nReceived sample:\n");
+		print_hex_dump(cmp_data + min_pos, max_pos - min_pos, min_pos);
+		return 1;
+	}
+
+	if (opts->mode & MODE_TX_ONLY)
+		tst_res(TPASS, "TX passed");
+
+	return 0;
+}
+
+static int stress_test_uart(struct g_opt *opts, int fd, unsigned char *data, off_t data_len)
+{
+	unsigned int loops = 0;
+	int status;
+
+	opts->cmp_buff = SAFE_MALLOC(data_len);
+	memset(opts->cmp_buff, 0, data_len);
+
+	do {
+		status = stress_test_uart_once(opts, fd, data, data_len);
+		memset(opts->cmp_buff, 0, data_len);
+	} while (++loops < opts->loops && !status);
+
+	free(opts->cmp_buff);
+
+	return status;
+}
+
+static int setup_uart(struct g_opt *opts, int open_mode, struct termios *old_term)
+{
+	struct termios new_term;
+	int fd;
+	int ret;
+
+	tst_res(TINFO, "Setting up %s speed %u hwflow=%u",
+	        opts->uart_dev, opts->baud_rate, opts->hwflow);
+
+	fd = SAFE_OPEN(opts->uart_dev, open_mode | O_NONBLOCK);
+
+	ret = tcgetattr(fd, old_term);
+	if (ret < 0)
+		tst_brk(TBROK, "tcgetattr() failed: %m\n");
+
+	new_term = *old_term;
+
+	/* or c_cflag |= BOTHER and c_ospeed for any speed */
+	ret = cfsetspeed(&new_term, opts->baud_rate);
+	if (ret < 0)
+		tst_brk(TBROK, "cfsetspeed(, %u) failed %m\n", opts->baud_rate);
+	cfmakeraw(&new_term);
+	new_term.c_cflag |= CREAD;
+	if (opts->hwflow)
+		new_term.c_cflag |= CRTSCTS;
+	else
+		new_term.c_cflag &= ~CRTSCTS;
+	new_term.c_cc[VMIN] = 64;
+	new_term.c_cc[VTIME] = 8;
+
+	ret = tcsetattr(fd, TCSANOW, &new_term);
+	if (ret < 0)
+		tst_brk(TBROK, "tcsetattr failed: %m\n");
+
+	if (opts->do_termios) {
+		ret = tcflush(fd, TCIFLUSH);
+		if (ret < 0)
+			tst_brk(TBROK, "tcflush failed: %m\n");
+	}
+
+	ret = fcntl(fd, F_SETFL, 0);
+	if (ret)
+		printf("Failed to remove nonblock mode\n");
+
+	return fd;
+}
+
+static void restore_uart(int fd, struct termios *old_term)
+{
+	int ret = tcsetattr(fd, TCSAFLUSH, old_term);
+	if (ret)
+		printf("tcsetattr() of old ones failed: %m\n");
+}
+
+static void print_counters(const char *prefix,
+                           struct serial_icounter_struct *old,
+                           struct serial_icounter_struct *new)
+{
+#define CNT(x) (new->x - old->x)
+	printf("%scts: %d dsr: %d rng: %d dcd: %d rx: %d tx: %d "
+		"frame %d ovr %d par: %d brk: %d buf_ovrr: %d\n", prefix,
+		CNT(cts), CNT(dsr), CNT(rng), CNT(dcd), CNT(rx),
+		CNT(tx), CNT(frame), CNT(overrun), CNT(parity),
+		CNT(brk), CNT(buf_overrun));
+#undef CNT
+}
+
+unsigned char *data;
+static long data_len;
+
+static void one_run(unsigned int baud_rate)
+{
+	struct serial_icounter_struct old_counters;
+	struct serial_icounter_struct new_counters;
+	int ret, fd_rx, fd_tx;
+	struct termios old_term_rx, old_term_tx;
+
+	struct g_opt opts_in = {
+		.loops = 1,
+		.do_termios = 1,
+		.uart_dev = uart_params.rx,
+		.baud_rate = baud_rate,
+		.hwflow = uart_params.hwflow,
+		.mode = MODE_RX_ONLY,
+	};
+
+	struct g_opt opts_out = {
+		.loops = 1,
+		.do_termios = 1,
+		.uart_dev = uart_params.tx,
+		.baud_rate = baud_rate,
+		.hwflow = uart_params.hwflow,
+		.mode = MODE_TX_ONLY,
+	};
+
+	fd_rx = setup_uart(&opts_in, O_RDONLY, &old_term_rx);
+
+	if (!strcmp(uart_params.rx, uart_params.tx))
+		fd_tx = SAFE_OPEN(uart_params.tx, O_WRONLY);
+	else
+		fd_tx = setup_uart(&opts_out, O_WRONLY, &old_term_tx);
+
+	if (!SAFE_FORK()) {
+		ioctl(fd_rx, TIOCGICOUNT, &old_counters);
+		stress_test_uart(&opts_in, fd_rx, data, data_len);
+		ret = ioctl(fd_rx, TIOCGICOUNT, &new_counters);
+		if (ret > 0)
+			print_counters("RX:", &old_counters, &new_counters);
+		exit(0);
+	}
+
+	if (!SAFE_FORK()) {
+		ioctl(fd_tx, TIOCGICOUNT, &old_counters);
+		stress_test_uart(&opts_out, fd_tx, data, data_len);
+		ret = ioctl(fd_tx, TIOCGICOUNT, &new_counters);
+		if (ret > 0)
+			print_counters("TX:", &old_counters, &new_counters);
+		exit(0);
+	}
+
+	SAFE_WAIT(NULL);
+	SAFE_WAIT(NULL);
+
+	restore_uart(fd_rx, &old_term_rx);
+
+	if (strcmp(uart_params.rx, uart_params.tx))
+		restore_uart(fd_tx, &old_term_tx);
+
+	close(fd_rx);
+	close(fd_tx);
+}
+
+void run(void)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(uart_params.baud_rates); i++) {
+		if (!uart_params.baud_rates[i])
+			return;
+		one_run(uart_params.baud_rates[i]);
+	}
+}
+
+static void map_file(const char *fname)
+{
+	struct stat st;
+	int fd;
+
+	fd = SAFE_OPEN(fname, O_RDONLY);
+
+	SAFE_FSTAT(fd, &st);
+
+	data_len = st.st_size;
+
+	data = SAFE_MMAP(NULL, data_len, PROT_READ,
+	                 MAP_SHARED | MAP_LOCKED | MAP_POPULATE, fd, 0);
+
+	tst_res(TINFO, "Mapped file '%s' size %li bytes", fname, data_len);
+
+	SAFE_CLOSE(fd);
+}
+
+static void map_buffer(long buf_size)
+{
+	size_t i;
+
+	data_len = buf_size;
+
+	data = SAFE_MMAP(NULL, data_len, PROT_READ | PROT_WRITE,
+	                 MAP_ANONYMOUS | MAP_SHARED | MAP_LOCKED, -1, 0);
+
+	long *p = (void*)data;
+
+	srandom(time(NULL));
+
+	for (i = 0; i < data_len / sizeof(long); i++)
+		p[i] = random();
+
+	tst_res(TINFO, "Mapped anynymous memory size %li bytes", data_len);
+}
+
+static char *fname;
+static char *buf_size;
+
+static int invalid_baudrate(long baudrate)
+{
+	if (baudrate < 0)
+		return 1;
+
+	if (baudrate > 230400)
+		return 1;
+	//TODO: do we have to for standard speeds?
+
+	return 0;
+}
+
+static void parse_baudrate(struct tst_json_buf *buf, struct tst_json_val *val)
+{
+	unsigned int pos = 0;
+
+	memset(uart_params.baud_rates, 0, sizeof(uart_params.baud_rates));
+
+	if (val->type != TST_JSON_ARR) {
+		tst_json_err(buf, "baudrate must be array of integers");
+		return;
+	}
+
+	TST_JSON_ARR_FOREACH(buf, val) {
+		if (val->type != TST_JSON_INT) {
+			tst_json_err(buf, "expected integer");
+			return;
+		}
+
+		if (invalid_baudrate(val->val_int)) {
+			tst_json_err(buf, "invalid baudrate");
+			return;
+		}
+
+		if (pos >= ARRAY_SIZE(uart_params.baud_rates)) {
+			tst_json_err(buf, "baudrates array too long");
+			return;
+		}
+
+		uart_params.baud_rates[pos++] = val->val_int;
+	}
+
+	if (pos <= 0) {
+		tst_json_err(buf, "at least one baudrate is required");
+		return;
+	}
+}
+
+static void setup(void)
+{
+	long size = 1024;
+
+	if (fname && buf_size)
+		tst_brk(TBROK, "Only one of -f and -s could be set!");
+
+	if (buf_size)
+		tst_parse_long(buf_size, &size, 0, LONG_MAX);
+
+	struct tst_json_buf *json = tst_hwconf_get();
+	char buf[128];
+	struct tst_json_val val = {.buf = buf, .buf_size = sizeof(buf)};
+
+	TST_JSON_OBJ_FOREACH(json, &val) {
+		if (!strcmp(val.id, "uid")) {
+			continue;
+		} else if (!strcmp(val.id, "hwflow")) {
+			if (val.type != TST_JSON_INT)
+				tst_json_err(json, "hwflow must be int");
+			else
+				uart_params.hwflow = !!val.val_int;
+		} else if (!strcmp(val.id, "tx")) {
+			if (val.type != TST_JSON_STR)
+				tst_json_err(json, "tx must be string");
+			else
+				strcpy(uart_params.tx, val.val_str);
+		} else if (!strcmp(val.id, "rx")) {
+			if (val.type != TST_JSON_STR)
+				tst_json_err(json, "rx must be string");
+			else
+				strcpy(uart_params.rx, val.val_str);
+		} else if (!strcmp(val.id, "tx")) {
+			if (val.type != TST_JSON_STR)
+				tst_json_err(json, "tx must be string");
+			else
+				strcpy(uart_params.tx, val.val_str);
+		} else if (!strcmp(val.id, "baud_rates")) {
+			parse_baudrate(json, &val);
+		} else {
+			tst_json_err(json, "Invalid attribute '%s'", val.id);
+		}
+	}
+
+	if (tst_json_is_err(json)) {
+		tst_json_err_print(stderr, json);
+		tst_brk(TBROK, "Invalid parameters!");
+	}
+
+	if (!uart_params.rx[0] || !uart_params.tx[0])
+		tst_brk(TBROK, "Missing UART RX or TX");
+
+	if (fname)
+		map_file(fname);
+	else
+		map_buffer(size);
+}
+
+static struct tst_test test = {
+	.setup = setup,
+	.test_all = run,
+	.options = (struct tst_option[]) {
+		{"f:",  &fname,    "-f       Binary file for transfers"},
+		{"s:",  &buf_size, "-s       Binary buffer size"},
+		{}
+	},
+	.needs_hardware = "UART-loopback",
+	.forks_child = 1,
+};
-- 
2.26.2


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [LTP] [Automated-testing] [PATCH v2 1/5] lib: tst_cmd: Make tst_cmd() usable for global paths
  2021-02-24 16:50 ` [LTP] [PATCH v2 1/5] lib: tst_cmd: Make tst_cmd() usable for global paths Cyril Hrubis
@ 2021-02-24 17:57   ` Petr Vorel
  0 siblings, 0 replies; 15+ messages in thread
From: Petr Vorel @ 2021-02-24 17:57 UTC (permalink / raw)
  To: ltp

> We need to skip the check if the path to the binary starts with '/'.
Good point!

Reviewed-by: Petr Vorel <pvorel@suse.cz>

Kind regards,
Petr

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [LTP] [Automated-testing] [PATCH v2 0/5] Another attempt at hardware discovery
  2021-02-24 16:50 [LTP] [PATCH v2 0/5] Another attempt at hardware discovery Cyril Hrubis
                   ` (4 preceding siblings ...)
  2021-02-24 16:50 ` [LTP] [PATCH v2 5/5] testcases: uart01: Add Cyril Hrubis
@ 2021-02-26 19:20 ` Tim.Bird
  2021-03-01 11:57   ` Cyril Hrubis
  2021-03-01 10:30 ` [LTP] " Cixi Geng
  6 siblings, 1 reply; 15+ messages in thread
From: Tim.Bird @ 2021-02-26 19:20 UTC (permalink / raw)
  To: ltp

Here are some questions and feedback on the proposal.

> -----Original Message-----
> From: automated-testing@lists.yoctoproject.org <automated-testing@lists.yoctoproject.org> On Behalf Of Cyril Hrubis
> 
> This is a second attempt on hardware discovery LTP support. The main
> difference between the previous attempts is that this version uses JSON,
> which allows us propagate structured data to the test.

JSON seems like a good choice.  In my experience, if a test is written
in a language where json-handling is not easy (e.g. shell scripts), there
are options for dealing with this.  For example, you could require 'jq'
on the system where the shell script runs.  It's not that bad of a dependency
IMHO.

> 
> This is still an early protototype but I'm sending it out to get more
> feedback before I continue to work on it.
> 
> So how is this supposed to work:
> 
> * Test that needs particular hardware sets the needs_hardware filed in
>   the test test structure. This is a free form string, in the uart
>   example it's called 'UART-loopback'. If we ever add a test for i2c
>   eeprom it would be probably called 'I2C-eeprom', etc.

Sounds good.  It will be good to standardize on these strings
in the long run.  For now, just picking strings and trying to reconcile
them later seems OK.

> 
> * The test library takes this and passes it to the hardware discovery
>   script/binary.

This is probably in the patch set, but how is this invoked?
Does the script have a well-known name or location?

> The example script that is included in this patchset
>   just hardcodes configuration for a usb-to-serial cable. In real world
>   lab this would be either prepared for each board specifically and
>   injected to the filesystem before the test happens, or may be a simple
>   script that calls curl with a request to a lab sever, etc.

Either of these seem like workable approaches.  In the case of
injecting it into the filesystem, do you have any
ideas where you would inject these?  For example,
would they go somewhere under /etc, or in /usr/test/conf?

Is this something that only the hardware discovery program
needs to know?  Or do tests use this data directly?

This seems like a rendezvous point between
the hardware discovery program and the lab management
system, so IMHO it should be well-defined.

> 
> * The output from the script is a JSON object. If there is a need to
>   reconfigure lab hardware before the test, the JSON contains a path to
>   a script/binary that has to be called to do so.

That's interesting.

> Again this may be a
>   script that calls curl with a request to a lab sever which would, for
>   example, interconnect different serial ports with relays.
> 
>   The parameter to that script is an unique ID for the hardware
>   configuration that is listed in each hardware configuration in the
>   hwconfs array of objects that follows.
> 
>   I'm not sure if this actually belongs there, maybe it would be
>   cleaner to have one reconfigure script for the whole LTP and we would
>   pass the needs_hardware content as well as the unique ID, e.g.
> 
>   'hardware-reconfigure.sh UART-loopback ttyUSB0-ttyS0'
> 
>   but that is a minor detail that could be easily sorted out later.
> 
> 
>   The most important part of the JSON is the hwconfs array, which
>   consists of hardware description objects, which, apart form the uid,
>   are not interpreted by the library, but rather passed to the test. The
>   test library loops over the array and forks a testrun for each entry
>   in the array.
> 
>   Each iteration of the test then gets it's parameters as a JSON object.
>   In the case of the UART one of the objects in the array looks like:
> 
>   {
>     "uid": "ttyUSB0-ttyUSB0-01",
>     "rx": "ttyUSB0",
>     "tx": "ttyUSB0",
>     "hwflow": 0,
>     "baud_rates": [
>      9600,
>      19200
>     ]
>   }
> 
>   Which is mostly self-explanatory, the test then parses the structure
>   and executes one test per each baud rate.
> 
>   What is still missing is the ability to pass the JSON hardware
>   description directly to the test, so that we can execute the test
>   manually, but that would be fairly easy to add.

OK - let me describe what I had in mind for a test where the serial
port was not in loopback mode, to see how it relates to the proposed
architecture.  The gpio version of this is implemented in the board
farm REST API, but the serial port testing version of this is still
under consideration:

Here's how the test would work, for a serial port transmission test where
the transmitting side of the serial line was on the DUT and the other side was
managed by the lab hardware:
 - 1) the test discovers the hardware for DUT endpoint of the serial line
    using the hardware discovery program
 - 2) the test discovers the hardware for the lab endpoint (receiving end)
    of the serial line using the hardware discovery program
 - 3) the test discovers the available baud rates from the hardware discovery
    program
 - 4) the test configures the lab endpoint for reception
 - 5) the test configures the DUT endpoint for transmission
 - 6) the test loops over the baud rate values, doing the following (7-13):
    - 7) use a curl interface or local program to set the receive baud rate of the lab endpoint
    - 8) use a curl interface or local program to request serial capture of the lab endpoint
    - 9) configure the baud rate of the DUT endpoint
    - 10) send data through the DUT endpoint
    - 11) use a curl interface or local program to cause the lab endpoint to stop capturing data
    - 12) use a curl interface or local program to request the capture data from the lab endpoint
    - 13) compare data that was received with the data that was intended to be transmitted

These roles could also be reversed (ie, have the lab endpoint be the sender
and the DUT endpoint be the receiver).

The process of configuring the baud rate seems like it corresponds
with the hardware_reconfigure.sh script.  But maybe you were only
thinking of have this actually set up physical connections, and not
manage connection/bus settings.

In any event, maybe the hardware_reconfigure.sh script could know
about 'start data capture', 'stop data capture', and 'get captured data'
operations, or maybe this should be a separate script.

This is structured in a generic way, because I think different tests
(such as audio testing, video testing, power measurement testing,
CAN bus testing, i2C testing, gpio testing, etc. can all be fit into this
model of testing by:
  discovering bus endpoints, configuring bus parameters,
  transmitting or generating data, capturing data
  comparing data

So far, I don't see anything in LTP proposal that precludes this model of testing.
There may be more to add - either extending hardware_reconfigure.sh
or adding a new command that a test would use interactively to
cause data to be exchanged and allow it to be compared.

So - it looks good to me!
 -- Tim

> 
> Cyril Hrubis (5):
>   lib: tst_cmd: Make tst_cmd() usable for global paths
>   lib: Add minimalistic json parser
>   lib: Add hardware discovery code
>   Sample hardware discovery and reconfigure scripts
>   testcases: uart01: Add.
> 
>  hardware-discovery.sh                         |  36 +
>  hardware-reconfigure.sh                       |   3 +
>  include/tst_hwconf.h                          |  13 +
>  include/tst_json.h                            | 177 +++++
>  include/tst_test.h                            |   3 +
>  lib/tst_cmd.c                                 |   2 +-
>  lib/tst_hardware.c                            | 218 ++++++
>  lib/tst_hardware.h                            |  83 +++
>  lib/tst_json.c                                | 679 ++++++++++++++++++
>  lib/tst_test.c                                |  30 +
>  runtest/device_drivers                        |   2 +
>  testcases/kernel/device-drivers/Makefile      |   1 +
>  .../kernel/device-drivers/uart/.gitignore     |   1 +
>  testcases/kernel/device-drivers/uart/Makefile |   3 +
>  testcases/kernel/device-drivers/uart/uart01.c | 620 ++++++++++++++++
>  15 files changed, 1870 insertions(+), 1 deletion(-)
>  create mode 100755 hardware-discovery.sh
>  create mode 100755 hardware-reconfigure.sh
>  create mode 100644 include/tst_hwconf.h
>  create mode 100644 include/tst_json.h
>  create mode 100644 lib/tst_hardware.c
>  create mode 100644 lib/tst_hardware.h
>  create mode 100644 lib/tst_json.c
>  create mode 100644 runtest/device_drivers
>  create mode 100644 testcases/kernel/device-drivers/uart/.gitignore
>  create mode 100644 testcases/kernel/device-drivers/uart/Makefile
>  create mode 100644 testcases/kernel/device-drivers/uart/uart01.c
> 
> --
> 2.26.2


^ permalink raw reply	[flat|nested] 15+ messages in thread

* [LTP] [Automated-testing] [PATCH v2 2/5] lib: Add minimalistic json parser
  2021-02-24 16:50 ` [LTP] [PATCH v2 2/5] lib: Add minimalistic json parser Cyril Hrubis
@ 2021-03-01  7:38   ` Petr Vorel
  2021-11-05 10:56     ` Richard Palethorpe
  0 siblings, 1 reply; 15+ messages in thread
From: Petr Vorel @ 2021-03-01  7:38 UTC (permalink / raw)
  To: ltp

Hi Cyril,

Reviewed-by: Petr Vorel <pvorel@suse.cz>

Nice work!

> +static int eatws(struct tst_json_buf *buf)
> +{
> +	while (!buf_empty(buf)) {
> +		switch (buf->json[buf->off]) {
> +		case ' ':
> +		case '\t':
> +		case '\n':
> +		case '\f':
Shouldn't there be '\r' as well? (handled at copy_str()).
> +		break;
> +		default:
> +			goto ret;
> +		}
> +
> +		buf->off += 1;
> +	}
> +ret:
> +	return buf_empty(buf);
> +}


Kind regards,
Petr

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [LTP] [PATCH v2 0/5] Another attempt at hardware discovery
  2021-02-24 16:50 [LTP] [PATCH v2 0/5] Another attempt at hardware discovery Cyril Hrubis
                   ` (5 preceding siblings ...)
  2021-02-26 19:20 ` [LTP] [Automated-testing] [PATCH v2 0/5] Another attempt at hardware discovery Tim.Bird
@ 2021-03-01 10:30 ` Cixi Geng
  2021-03-01 13:24   ` Cyril Hrubis
  6 siblings, 1 reply; 15+ messages in thread
From: Cixi Geng @ 2021-03-01 10:30 UTC (permalink / raw)
  To: ltp

Cyril Hrubis <chrubis@suse.cz> ?2021?2?25??? ??12:49???
>
> This is a second attempt on hardware discovery LTP support. The main
> difference between the previous attempts is that this version uses JSON,
> which allows us propagate structured data to the test.
>
> This is still an early protototype but I'm seding it out to get more
> feedback before I continue to work on it.
>
> So how is this supposed to work:
>
> * Test that needs particular hardware sets the needs_hardware filed in
>   the test test structure. This is a free form string, in the uart
>   example it's called 'UART-loopback'. If we ever add a test for i2c
>   eeprom it would be probably called 'I2C-eeprom', etc.
I think this has many advantages over the first edition, In out Internal Test?
we use a similar way to define the test prerequires as JSON structor.[1]
here is My opinion, In the LTP testcase framework, we can set all the
prerequires
in a JSONfile, these prerquires include CONFIGS,  ABIs,
devices-path(sys?proc,dev etc.),
the SETUP stage  to check if the machine satisfy the defined-Json.
at the same time? we also need a scripts to generate a JSON-file from
the current machine to collect all the prerequires list.
In this way, the benefit not just device-driver, but alse common testcase
>
> * The test library takes this and passes it to the hardware discovery
>   script/binary. The example scipt that is included in this patchset
>   just hardcodes configuration for a usb-to-serial cable. In real world
>   lab this would be either prepared for each board specifically and
>   injected to the filesystem before the test happens, or may be a simple
>   script that calls curl with a request to a lab sever, etc.
>
> * The output from the script is a JSON object. If there is a need to
>   reconfigure lab hardware before the test, the JSON contains a path to
>   a script/binary that has to be called to do so. Again this may be a
>   script that calls curl with a request to a lab sever which would, for
>   example, interconnect different serial ports with relays.
>
>   The parameter to that script is an unique ID for the hardware
>   configuration that is listed in each hardware configuration in the
>   hwconfs array of objects that follows.
>
>   I'm not sure if this actually belongs there, maybe it would be
>   cleaner to have one reconfigure script for the whole LTP and we would
>   pass the needs_hardware content as well as the unique ID, e.g.
>
>   'hardware-reconfigure.sh UART-loopback ttyUSB0-ttyS0'
>
>   but that is a minor detail that could be easily sorted out later.
>
>
>   The most important part of the JSON is the hwconfs array, which
>   consists of hardware description objects, which, apart form the uid,
>   are not interpreted by the library, but rather passed to the test. The
>   test library loops over the array and forks a testrun for each entry
>   in the array.
>
>   Each iteration of the test then gets it's parameters as a JSON object.
>   In the case of the UART one of the objects in the array looks like:
>
>   {
>     "uid": "ttyUSB0-ttyUSB0-01",
>     "rx": "ttyUSB0",
>     "tx": "ttyUSB0",
>     "hwflow": 0,
>     "baud_rates": [
>      9600,
>      19200
>     ]
>   }
>
>   Which is mostly self-explanatory, the test then parses the structure
>   and executes one test per each baud rate.
>
>   What is still missing is the ability to pass the JSON hardware
>   description directly to the test, so that we can execute the test
>   manually, but that would be fairly easy to add.
>
> Cyril Hrubis (5):
>   lib: tst_cmd: Make tst_cmd() usable for global paths
>   lib: Add minimalistic json parser
>   lib: Add hardware discovery code
>   Sample hardware discovery and reconfigure scripts
>   testcases: uart01: Add.
>
>  hardware-discovery.sh                         |  36 +
>  hardware-reconfigure.sh                       |   3 +
>  include/tst_hwconf.h                          |  13 +
>  include/tst_json.h                            | 177 +++++
>  include/tst_test.h                            |   3 +
>  lib/tst_cmd.c                                 |   2 +-
>  lib/tst_hardware.c                            | 218 ++++++
>  lib/tst_hardware.h                            |  83 +++
>  lib/tst_json.c                                | 679 ++++++++++++++++++
>  lib/tst_test.c                                |  30 +
>  runtest/device_drivers                        |   2 +
>  testcases/kernel/device-drivers/Makefile      |   1 +
>  .../kernel/device-drivers/uart/.gitignore     |   1 +
>  testcases/kernel/device-drivers/uart/Makefile |   3 +
>  testcases/kernel/device-drivers/uart/uart01.c | 620 ++++++++++++++++
>  15 files changed, 1870 insertions(+), 1 deletion(-)
>  create mode 100755 hardware-discovery.sh
>  create mode 100755 hardware-reconfigure.sh
>  create mode 100644 include/tst_hwconf.h
>  create mode 100644 include/tst_json.h
>  create mode 100644 lib/tst_hardware.c
>  create mode 100644 lib/tst_hardware.h
>  create mode 100644 lib/tst_json.c
>  create mode 100644 runtest/device_drivers
>  create mode 100644 testcases/kernel/device-drivers/uart/.gitignore
>  create mode 100644 testcases/kernel/device-drivers/uart/Makefile
>  create mode 100644 testcases/kernel/device-drivers/uart/uart01.c
>
> --
> 2.26.2
>

[1] https://github.com/gengcixi/kernel_feature_check

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [LTP] [Automated-testing] [PATCH v2 0/5] Another attempt at hardware discovery
  2021-02-26 19:20 ` [LTP] [Automated-testing] [PATCH v2 0/5] Another attempt at hardware discovery Tim.Bird
@ 2021-03-01 11:57   ` Cyril Hrubis
  2021-11-05 10:30     ` Richard Palethorpe
  0 siblings, 1 reply; 15+ messages in thread
From: Cyril Hrubis @ 2021-03-01 11:57 UTC (permalink / raw)
  To: ltp

Hi!
> > * Test that needs particular hardware sets the needs_hardware filed in
> >   the test test structure. This is a free form string, in the uart
> >   example it's called 'UART-loopback'. If we ever add a test for i2c
> >   eeprom it would be probably called 'I2C-eeprom', etc.
> 
> Sounds good.  It will be good to standardize on these strings
> in the long run.  For now, just picking strings and trying to reconcile
> them later seems OK.

That's what I'm expecting there as well.

> > 
> > * The test library takes this and passes it to the hardware discovery
> >   script/binary.
> 
> This is probably in the patch set, but how is this invoked?
> Does the script have a well-known name or location?

There is a fallback to a well-known location that can be overrided with
an environment varible. This is not well designed at this point, I just
wrote a minimal amount of code for the prototype to run.

> > The example script that is included in this patchset
> >   just hardcodes configuration for a usb-to-serial cable. In real world
> >   lab this would be either prepared for each board specifically and
> >   injected to the filesystem before the test happens, or may be a simple
> >   script that calls curl with a request to a lab sever, etc.
> 
> Either of these seem like workable approaches.  In the case of
> injecting it into the filesystem, do you have any
> ideas where you would inject these?  For example,
> would they go somewhere under /etc, or in /usr/test/conf?

No idea, so far my patchset just puts it into $LTPROOT, but I guess that
/usr/test/ would be much better place. Do we have a proposal for the
/usr/test/ hierarchy somewhere?

> Is this something that only the hardware discovery program
> needs to know?  Or do tests use this data directly?

Currently it's used by the LTP test library, so it's executed by the
test binary before it forks a child that actually runs the test.

In the future it could be executed by the test driver (e.g. replacement
for ltp-pan I'm working on), which could then pass the pieces of the
JSON that describe the hardware to the test for each test invocation.
This is still under development, but I do not think that these details
matter at all, what we need at this point is to describe generic enough
information flow.

> This seems like a rendezvous point between
> the hardware discovery program and the lab management
> system, so IMHO it should be well-defined.

Couldn't agree more.

> > 
> > * The output from the script is a JSON object. If there is a need to
> >   reconfigure lab hardware before the test, the JSON contains a path to
> >   a script/binary that has to be called to do so.
> 
> That's interesting.
> 
> > Again this may be a
> >   script that calls curl with a request to a lab sever which would, for
> >   example, interconnect different serial ports with relays.
> > 
> >   The parameter to that script is an unique ID for the hardware
> >   configuration that is listed in each hardware configuration in the
> >   hwconfs array of objects that follows.
> > 
> >   I'm not sure if this actually belongs there, maybe it would be
> >   cleaner to have one reconfigure script for the whole LTP and we would
> >   pass the needs_hardware content as well as the unique ID, e.g.
> > 
> >   'hardware-reconfigure.sh UART-loopback ttyUSB0-ttyS0'
> > 
> >   but that is a minor detail that could be easily sorted out later.
> > 
> > 
> >   The most important part of the JSON is the hwconfs array, which
> >   consists of hardware description objects, which, apart form the uid,
> >   are not interpreted by the library, but rather passed to the test. The
> >   test library loops over the array and forks a testrun for each entry
> >   in the array.
> > 
> >   Each iteration of the test then gets it's parameters as a JSON object.
> >   In the case of the UART one of the objects in the array looks like:
> > 
> >   {
> >     "uid": "ttyUSB0-ttyUSB0-01",
> >     "rx": "ttyUSB0",
> >     "tx": "ttyUSB0",
> >     "hwflow": 0,
> >     "baud_rates": [
> >      9600,
> >      19200
> >     ]
> >   }
> > 
> >   Which is mostly self-explanatory, the test then parses the structure
> >   and executes one test per each baud rate.
> > 
> >   What is still missing is the ability to pass the JSON hardware
> >   description directly to the test, so that we can execute the test
> >   manually, but that would be fairly easy to add.
> 
> OK - let me describe what I had in mind for a test where the serial
> port was not in loopback mode, to see how it relates to the proposed
> architecture.  The gpio version of this is implemented in the board
> farm REST API, but the serial port testing version of this is still
> under consideration:
> 
> Here's how the test would work, for a serial port transmission test where
> the transmitting side of the serial line was on the DUT and the other side was
> managed by the lab hardware:
>  - 1) the test discovers the hardware for DUT endpoint of the serial line
>     using the hardware discovery program
>  - 2) the test discovers the hardware for the lab endpoint (receiving end)
>     of the serial line using the hardware discovery program
>  - 3) the test discovers the available baud rates from the hardware discovery
>     program
>  - 4) the test configures the lab endpoint for reception
>  - 5) the test configures the DUT endpoint for transmission
>  - 6) the test loops over the baud rate values, doing the following (7-13):
>     - 7) use a curl interface or local program to set the receive baud rate of the lab endpoint
>     - 8) use a curl interface or local program to request serial capture of the lab endpoint
>     - 9) configure the baud rate of the DUT endpoint
>     - 10) send data through the DUT endpoint
>     - 11) use a curl interface or local program to cause the lab endpoint to stop capturing data
>     - 12) use a curl interface or local program to request the capture data from the lab endpoint
>     - 13) compare data that was received with the data that was intended to be transmitted
> 
> These roles could also be reversed (ie, have the lab endpoint be the sender
> and the DUT endpoint be the receiver).
> 
> The process of configuring the baud rate seems like it corresponds
> with the hardware_reconfigure.sh script.  But maybe you were only
> thinking of have this actually set up physical connections, and not
> manage connection/bus settings.
> 
> In any event, maybe the hardware_reconfigure.sh script could know
> about 'start data capture', 'stop data capture', and 'get captured data'
> operations, or maybe this should be a separate script.
>
> This is structured in a generic way, because I think different tests
> (such as audio testing, video testing, power measurement testing,
> CAN bus testing, i2C testing, gpio testing, etc. can all be fit into this
> model of testing by:
>   discovering bus endpoints, configuring bus parameters,
>   transmitting or generating data, capturing data
>   comparing data

I guess that we need two more generic scripts in the format then, one
called before the test starts and one called after the test has
finished.

So something as:

{
  "pre_test": "/path/to/pre_script.sh",
  "post_test": "/path/to/post_script.sh",
  "hwconfs": [
    ...
  ]
}

And these scripts would need to actually be called before each test and
get JSON as a parameter as well.

e.g. for a serial port described as:

{
  "uid": "ttyUSB0-ttyUSB0-01",
  "rx": "ttyUSB0",
  "tx": "ttyUSB0",
  "hwflow": 0,
  "baud_rates": [
   9600,
   19200
  ]
}

We would get following:

------------------------------

* pre_script.sh gets passed following JSON:

{
  "uid": "ttyUSB0-ttyUSB0-01",
  "rx": "ttyUSB0",
  "tx": "ttyUSB0",
  "hwflow": 0,
  "baud_rate": 9600
}

* test is executed

* post_script.sh gets passed following JSON

{
  "uid": "ttyUSB0-ttyUSB0-01",
}

------------------------------

* pre_script.sh gets passed following JSON

{
  "uid": "ttyUSB0-ttyUSB0-01",
  "rx": "ttyUSB0",
  "tx": "ttyUSB0",
  "hwflow": 0,
  "baud_rate": 19200
}

* test is executed

* post_script.sh gets passed following JSON

{
  "uid": "ttyUSB0-ttyUSB0-01",
}

------------------------------

This changes things a bit, this means that the pre_script has to be
executed before each test execution and it's actually passed a JSON data
rather than simple value. So the test would need to serialize the data
and send the request instead.

The question is how should we propagate data to the test from a
post_script.sh. I guess that the output should be JSON as well and in a
case of binary data it should include a path to a file, since binary
does not work well for JSON.

So in the case of UART capture test the output from post_script.sh would
be something as:

{
	"capture": "/path/to/capture/file.bin"
}

Also in a case that we want the lab to send some binary data to the UART
during the test the pre_script.sh would get something as:

{
  "uid": "ttyUSB0-ttyUSB0-01",
  "hwflow": 0,
  "baud_rate": 19200,
  "transmit": "/path/to/transmit/file.bin"
}

However this means that the test needs to listen even before we call
this script, which again complicates things a bit.

> So far, I don't see anything in LTP proposal that precludes this model of testing.
> There may be more to add - either extending hardware_reconfigure.sh
> or adding a new command that a test would use interactively to
> cause data to be exchanged and allow it to be compared.

Indeed, this is hard to desing especially since I do have very limited
set of usecases at the moment. But at least your UART case helped a lot,
thanks!

-- 
Cyril Hrubis
chrubis@suse.cz

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [LTP] [PATCH v2 0/5] Another attempt at hardware discovery
  2021-03-01 10:30 ` [LTP] " Cixi Geng
@ 2021-03-01 13:24   ` Cyril Hrubis
  0 siblings, 0 replies; 15+ messages in thread
From: Cyril Hrubis @ 2021-03-01 13:24 UTC (permalink / raw)
  To: ltp

Hi!
> I think this has many advantages over the first edition, In out Internal Test???
> we use a similar way to define the test prerequires as JSON structor.[1]
> here is My opinion, In the LTP testcase framework, we can set all the
> prerequires
> in a JSONfile, these prerquires include CONFIGS,  ABIs,
> devices-path(sys???proc,dev etc.),
> the SETUP stage  to check if the machine satisfy the defined-Json.
> at the same time??? we also need a scripts to generate a JSON-file from
> the current machine to collect all the prerequires list.
> In this way, the benefit not just device-driver, but alse common testcase

We actually have something like this implemented. We generate a big JSON
file on LTP build that describes the whole testsuite, but it includes
more than just pre-requisites, have a look at docparse/metadata.json
that is generated in recent LTP builds.

As for kernel config dependencies we have needs_kconfigs array, which is
actually an array of boolean expressions on kernel config options where
each of them has to be true in order for a test to continue. So to find
out if we have a suitable kernel config for a test we have to check if
all are true in:

"tests": {
	"test_name": {
		"needs_kconfigs": [
			"CONFIG_FOO=y | CONFIG_BAR=m",
			"!CONFIG_DAR=y"
		]
	}
}

We do not have the "file" part that would match the functionality you
have in your code but that shouldn't be hard to add.

-- 
Cyril Hrubis
chrubis@suse.cz

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [LTP] [Automated-testing] [PATCH v2 0/5] Another attempt at hardware discovery
  2021-03-01 11:57   ` Cyril Hrubis
@ 2021-11-05 10:30     ` Richard Palethorpe
  0 siblings, 0 replies; 15+ messages in thread
From: Richard Palethorpe @ 2021-11-05 10:30 UTC (permalink / raw)
  To: Cyril Hrubis
  Cc: ceh, michals, automated-testing, orsonzhai, ltp, automated-testing

Hello All,

"Cyril Hrubis" <chrubis@suse.cz> writes:

> Hi!
>> > * Test that needs particular hardware sets the needs_hardware filed in
>> >   the test test structure. This is a free form string, in the uart
>> >   example it's called 'UART-loopback'. If we ever add a test for i2c
>> >   eeprom it would be probably called 'I2C-eeprom', etc.
>> 
>> Sounds good.  It will be good to standardize on these strings
>> in the long run.  For now, just picking strings and trying to reconcile
>> them later seems OK.
>
> That's what I'm expecting there as well.
>
>> > 
>> > * The test library takes this and passes it to the hardware discovery
>> >   script/binary.
>> 
>> This is probably in the patch set, but how is this invoked?
>> Does the script have a well-known name or location?
>
> There is a fallback to a well-known location that can be overrided with
> an environment varible. This is not well designed at this point, I just
> wrote a minimal amount of code for the prototype to run.
>
>> > The example script that is included in this patchset
>> >   just hardcodes configuration for a usb-to-serial cable. In real world
>> >   lab this would be either prepared for each board specifically and
>> >   injected to the filesystem before the test happens, or may be a simple
>> >   script that calls curl with a request to a lab sever, etc.
>> 
>> Either of these seem like workable approaches.  In the case of
>> injecting it into the filesystem, do you have any
>> ideas where you would inject these?  For example,
>> would they go somewhere under /etc, or in /usr/test/conf?
>
> No idea, so far my patchset just puts it into $LTPROOT, but I guess that
> /usr/test/ would be much better place. Do we have a proposal for the
> /usr/test/ hierarchy somewhere?
>
>> Is this something that only the hardware discovery program
>> needs to know?  Or do tests use this data directly?
>
> Currently it's used by the LTP test library, so it's executed by the
> test binary before it forks a child that actually runs the test.
>
> In the future it could be executed by the test driver (e.g. replacement
> for ltp-pan I'm working on), which could then pass the pieces of the
> JSON that describe the hardware to the test for each test invocation.
> This is still under development, but I do not think that these details
> matter at all, what we need at this point is to describe generic enough
> information flow.
>
>> This seems like a rendezvous point between
>> the hardware discovery program and the lab management
>> system, so IMHO it should be well-defined.
>
> Couldn't agree more.
>
>> > 
>> > * The output from the script is a JSON object. If there is a need to
>> >   reconfigure lab hardware before the test, the JSON contains a path to
>> >   a script/binary that has to be called to do so.
>> 
>> That's interesting.
>> 
>> > Again this may be a
>> >   script that calls curl with a request to a lab sever which would, for
>> >   example, interconnect different serial ports with relays.
>> > 
>> >   The parameter to that script is an unique ID for the hardware
>> >   configuration that is listed in each hardware configuration in the
>> >   hwconfs array of objects that follows.
>> > 
>> >   I'm not sure if this actually belongs there, maybe it would be
>> >   cleaner to have one reconfigure script for the whole LTP and we would
>> >   pass the needs_hardware content as well as the unique ID, e.g.
>> > 
>> >   'hardware-reconfigure.sh UART-loopback ttyUSB0-ttyS0'
>> > 
>> >   but that is a minor detail that could be easily sorted out later.
>> > 
>> > 
>> >   The most important part of the JSON is the hwconfs array, which
>> >   consists of hardware description objects, which, apart form the uid,
>> >   are not interpreted by the library, but rather passed to the test. The
>> >   test library loops over the array and forks a testrun for each entry
>> >   in the array.
>> > 
>> >   Each iteration of the test then gets it's parameters as a JSON object.
>> >   In the case of the UART one of the objects in the array looks like:
>> > 
>> >   {
>> >     "uid": "ttyUSB0-ttyUSB0-01",
>> >     "rx": "ttyUSB0",
>> >     "tx": "ttyUSB0",
>> >     "hwflow": 0,
>> >     "baud_rates": [
>> >      9600,
>> >      19200
>> >     ]
>> >   }
>> > 
>> >   Which is mostly self-explanatory, the test then parses the structure
>> >   and executes one test per each baud rate.
>> > 
>> >   What is still missing is the ability to pass the JSON hardware
>> >   description directly to the test, so that we can execute the test
>> >   manually, but that would be fairly easy to add.
>> 
>> OK - let me describe what I had in mind for a test where the serial
>> port was not in loopback mode, to see how it relates to the proposed
>> architecture.  The gpio version of this is implemented in the board
>> farm REST API, but the serial port testing version of this is still
>> under consideration:
>> 
>> Here's how the test would work, for a serial port transmission test where
>> the transmitting side of the serial line was on the DUT and the other side was
>> managed by the lab hardware:
>>  - 1) the test discovers the hardware for DUT endpoint of the serial line
>>     using the hardware discovery program
>>  - 2) the test discovers the hardware for the lab endpoint (receiving end)
>>     of the serial line using the hardware discovery program
>>  - 3) the test discovers the available baud rates from the hardware discovery
>>     program
>>  - 4) the test configures the lab endpoint for reception
>>  - 5) the test configures the DUT endpoint for transmission
>>  - 6) the test loops over the baud rate values, doing the following (7-13):
>>     - 7) use a curl interface or local program to set the receive baud rate of the lab endpoint
>>     - 8) use a curl interface or local program to request serial capture of the lab endpoint
>>     - 9) configure the baud rate of the DUT endpoint
>>     - 10) send data through the DUT endpoint
>>     - 11) use a curl interface or local program to cause the lab endpoint to stop capturing data
>>     - 12) use a curl interface or local program to request the capture data from the lab endpoint
>>     - 13) compare data that was received with the data that was intended to be transmitted
>> 
>> These roles could also be reversed (ie, have the lab endpoint be the sender
>> and the DUT endpoint be the receiver).
>> 
>> The process of configuring the baud rate seems like it corresponds
>> with the hardware_reconfigure.sh script.  But maybe you were only
>> thinking of have this actually set up physical connections, and not
>> manage connection/bus settings.
>> 
>> In any event, maybe the hardware_reconfigure.sh script could know
>> about 'start data capture', 'stop data capture', and 'get captured data'
>> operations, or maybe this should be a separate script.
>>
>> This is structured in a generic way, because I think different tests
>> (such as audio testing, video testing, power measurement testing,
>> CAN bus testing, i2C testing, gpio testing, etc. can all be fit into this
>> model of testing by:
>>   discovering bus endpoints, configuring bus parameters,
>>   transmitting or generating data, capturing data
>>   comparing data
>
> I guess that we need two more generic scripts in the format then, one
> called before the test starts and one called after the test has
> finished.
>
> So something as:
>
> {
>   "pre_test": "/path/to/pre_script.sh",
>   "post_test": "/path/to/post_script.sh",
>   "hwconfs": [
>     ...
>   ]
> }

How about we allow an arbitrary list of callbacks for the initial
implementation?

e.g.

{
  "cmds" : {
    "activate-robot-1": "robocmd 1",
    "launch-missiles": "echo 'fire!' > /dev/stty1",
    ...
  }
  "hwconfs": [ ... ]
}

As we add tests it may turn out there are only a limited number of
callbacks practically possible. Then we can standardise on those.

>
> And these scripts would need to actually be called before each test and
> get JSON as a parameter as well.
>
> e.g. for a serial port described as:
>
> {
>   "uid": "ttyUSB0-ttyUSB0-01",
>   "rx": "ttyUSB0",
>   "tx": "ttyUSB0",
>   "hwflow": 0,
>   "baud_rates": [
>    9600,
>    19200
>   ]
> }
>
> We would get following:
>
> ------------------------------
>
> * pre_script.sh gets passed following JSON:
>
> {
>   "uid": "ttyUSB0-ttyUSB0-01",
>   "rx": "ttyUSB0",
>   "tx": "ttyUSB0",
>   "hwflow": 0,
>   "baud_rate": 9600
> }
>
> * test is executed
>
> * post_script.sh gets passed following JSON
>
> {
>   "uid": "ttyUSB0-ttyUSB0-01",
> }
>
> ------------------------------
>
> * pre_script.sh gets passed following JSON
>
> {
>   "uid": "ttyUSB0-ttyUSB0-01",
>   "rx": "ttyUSB0",
>   "tx": "ttyUSB0",
>   "hwflow": 0,
>   "baud_rate": 19200
> }
>
> * test is executed
>
> * post_script.sh gets passed following JSON
>
> {
>   "uid": "ttyUSB0-ttyUSB0-01",
> }
>
> ------------------------------
>
> This changes things a bit, this means that the pre_script has to be
> executed before each test execution and it's actually passed a JSON data
> rather than simple value. So the test would need to serialize the data
> and send the request instead.
>
> The question is how should we propagate data to the test from a
> post_script.sh. I guess that the output should be JSON as well and in a
> case of binary data it should include a path to a file, since binary
> does not work well for JSON.
>
> So in the case of UART capture test the output from post_script.sh would
> be something as:
>
> {
> 	"capture": "/path/to/capture/file.bin"
> }

Yes, or base64 encode it if the data is small. Or send

{ ...., "trailing_binary": 1 }\n

Then transmit the binary data after the '}\n'

>
> Also in a case that we want the lab to send some binary data to the UART
> during the test the pre_script.sh would get something as:
>
> {
>   "uid": "ttyUSB0-ttyUSB0-01",
>   "hwflow": 0,
>   "baud_rate": 19200,
>   "transmit": "/path/to/transmit/file.bin"
> }
>
> However this means that the test needs to listen even before we call
> this script, which again complicates things a bit.

Not if we make the cmds free form. Using JSON makes perfect sense
IMO. I'd like to combine efforts here and use the JSON parser for the
parallel test executor IPC.

>
>> So far, I don't see anything in LTP proposal that precludes this model of testing.
>> There may be more to add - either extending hardware_reconfigure.sh
>> or adding a new command that a test would use interactively to
>> cause data to be exchanged and allow it to be compared.
>
> Indeed, this is hard to desing especially since I do have very limited
> set of usecases at the moment. But at least your UART case helped a lot,
> thanks!


-- 
Thank you,
Richard.

-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [LTP] [Automated-testing] [PATCH v2 2/5] lib: Add minimalistic json parser
  2021-03-01  7:38   ` [LTP] [Automated-testing] " Petr Vorel
@ 2021-11-05 10:56     ` Richard Palethorpe
  2021-11-05 11:16       ` Cyril Hrubis
  0 siblings, 1 reply; 15+ messages in thread
From: Richard Palethorpe @ 2021-11-05 10:56 UTC (permalink / raw)
  To: Petr Vorel
  Cc: Carlos Hernandez, Michal Simek, automated-testing, Orson Zhai,
	ltp, automated-testing

Hello,

"Petr Vorel" <pvorel@suse.cz> writes:

> Hi Cyril,
>
> Reviewed-by: Petr Vorel <pvorel@suse.cz>
>
> Nice work!
>
>> +static int eatws(struct tst_json_buf *buf)
>> +{
>> +	while (!buf_empty(buf)) {
>> +		switch (buf->json[buf->off]) {
>> +		case ' ':
>> +		case '\t':
>> +		case '\n':
>> +		case '\f':
> Shouldn't there be '\r' as well? (handled at copy_str()).

Yeah, it seems to be in the spec.

At any rate, I wouldn't mind this patch being applied immediately
because there is more than one WIP it may be useful for.

Reviewed-by: Richard Palethorpe <rpalethorpe@suse.com>

>> +		break;
>> +		default:
>> +			goto ret;
>> +		}
>> +
>> +		buf->off += 1;
>> +	}
>> +ret:
>> +	return buf_empty(buf);
>> +}
>
>
> Kind regards,
> Petr
>
>
> -=-=-=-=-=-=-=-=-=-=-=-
> Links: You receive all messages sent to this group.
> View/Reply Online (#872): https://lists.yoctoproject.org/g/automated-testing/message/872
> Mute This Topic: https://lists.yoctoproject.org/mt/80881162/3616767
> Group Owner: automated-testing+owner@lists.yoctoproject.org
> Unsubscribe: https://lists.yoctoproject.org/g/automated-testing/unsub [rpalethorpe@suse.de]
> -=-=-=-=-=-=-=-=-=-=-=-


-- 
Thank you,
Richard.

-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [LTP] [Automated-testing] [PATCH v2 2/5] lib: Add minimalistic json parser
  2021-11-05 10:56     ` Richard Palethorpe
@ 2021-11-05 11:16       ` Cyril Hrubis
  0 siblings, 0 replies; 15+ messages in thread
From: Cyril Hrubis @ 2021-11-05 11:16 UTC (permalink / raw)
  To: Richard Palethorpe
  Cc: Carlos Hernandez, Michal Simek, automated-testing, Orson Zhai,
	ltp, automated-testing

Hi!
> Yeah, it seems to be in the spec.
> 
> At any rate, I wouldn't mind this patch being applied immediately
> because there is more than one WIP it may be useful for.

Meanwhile I did a bit more work on the parser and made sure that it
passes JSONTestSuite as well.

If you need a JSON parser in LTP we should vendor the latest version at:

https://github.com/metan-ucw/ujson

-- 
Cyril Hrubis
chrubis@suse.cz

-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2021-11-05 11:15 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-02-24 16:50 [LTP] [PATCH v2 0/5] Another attempt at hardware discovery Cyril Hrubis
2021-02-24 16:50 ` [LTP] [PATCH v2 1/5] lib: tst_cmd: Make tst_cmd() usable for global paths Cyril Hrubis
2021-02-24 17:57   ` [LTP] [Automated-testing] " Petr Vorel
2021-02-24 16:50 ` [LTP] [PATCH v2 2/5] lib: Add minimalistic json parser Cyril Hrubis
2021-03-01  7:38   ` [LTP] [Automated-testing] " Petr Vorel
2021-11-05 10:56     ` Richard Palethorpe
2021-11-05 11:16       ` Cyril Hrubis
2021-02-24 16:50 ` [LTP] [PATCH v2 3/5] lib: Add hardware discovery code Cyril Hrubis
2021-02-24 16:50 ` [LTP] [PATCH v2 4/5] Sample hardware discovery and reconfigure scripts Cyril Hrubis
2021-02-24 16:50 ` [LTP] [PATCH v2 5/5] testcases: uart01: Add Cyril Hrubis
2021-02-26 19:20 ` [LTP] [Automated-testing] [PATCH v2 0/5] Another attempt at hardware discovery Tim.Bird
2021-03-01 11:57   ` Cyril Hrubis
2021-11-05 10:30     ` Richard Palethorpe
2021-03-01 10:30 ` [LTP] " Cixi Geng
2021-03-01 13:24   ` Cyril Hrubis

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.