@@ -62,7 +62,7 @@ $(1):: | $$(abs_top_builddir)/$$(basename $$(subst -,.,$(1)))
endif
endef
-COMMON_TARGETS += testcases tools
+COMMON_TARGETS += testcases tools docparse
# Don't want to nuke the original files if we're installing in-build-tree.
ifneq ($(BUILD_TREE_STATE),$(BUILD_TREE_SRCDIR_INSTALL))
INSTALL_TARGETS += runtest scenario_groups testscripts
new file mode 100644
@@ -0,0 +1,2 @@
+/docparse
+/metadata.json
new file mode 100644
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2019 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir ?= ..
+
+include $(top_srcdir)/include/mk/env_pre.mk
+include $(top_srcdir)/include/mk/functions.mk
+
+MAKE_TARGETS := metadata.json
+HOST_MAKE_TARGETS := docparse
+
+INSTALL_DIR = metadata
+
+.PHONY: metadata.json
+
+metadata.json: docparse
+ $(abs_srcdir)/parse.sh > metadata.json
+
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
new file mode 100644
@@ -0,0 +1,299 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2019 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+#ifndef DATA_STORAGE_H__
+#define DATA_STORAGE_H__
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+enum data_type {
+ DATA_ARRAY,
+ DATA_HASH,
+ DATA_STRING,
+};
+
+struct data_node_array {
+ enum data_type type;
+ unsigned int array_len;
+ unsigned int array_used;
+ struct data_node *array[];
+};
+
+struct data_hash_elem {
+ struct data_node *node;
+ char *id;
+};
+
+struct data_node_hash {
+ enum data_type type;
+ unsigned int elems_len;
+ unsigned int elems_used;
+ struct data_hash_elem elems[];
+};
+
+struct data_node_string {
+ enum data_type type;
+ char val[];
+};
+
+struct data_node {
+ union {
+ enum data_type type;
+ struct data_node_hash hash;
+ struct data_node_array array;
+ struct data_node_string string;
+ };
+};
+
+static inline struct data_node *data_node_string(const char *string)
+{
+ size_t size = sizeof(struct data_node_string) + strlen(string) + 1;
+ struct data_node *node = malloc(size);
+
+ if (!node)
+ return NULL;
+
+ node->type = DATA_STRING;
+ strcpy(node->string.val, string);
+
+ return node;
+}
+
+#define MAX_ELEMS 20
+
+static inline struct data_node *data_node_hash(void)
+{
+ size_t size = sizeof(struct data_node_hash)
+ + MAX_ELEMS * sizeof(struct data_hash_elem);
+ struct data_node *node = malloc(size);
+
+ if (!node)
+ return NULL;
+
+ node->type = DATA_HASH;
+ node->hash.elems_len = MAX_ELEMS;
+ node->hash.elems_used = 0;
+
+ return node;
+}
+
+static inline struct data_node *data_node_array(void)
+{
+ size_t size = sizeof(struct data_node_array) +
+ + MAX_ELEMS * sizeof(struct data_node*);
+ struct data_node *node = malloc(size);
+
+ if (!node)
+ return NULL;
+
+ node->type = DATA_ARRAY;
+ node->array.array_len = MAX_ELEMS;
+ node->array.array_used = 0;
+
+ return node;
+}
+
+static inline int data_node_hash_add(struct data_node *self, const char *id, struct data_node *payload)
+{
+ if (self->type != DATA_HASH)
+ return 1;
+
+ struct data_node_hash *hash = &self->hash;
+
+ if (hash->elems_used == hash->elems_len)
+ return 1;
+
+ struct data_hash_elem *elem = &hash->elems[hash->elems_used++];
+
+ elem->node = payload;
+ elem->id = strdup(id);
+
+ return 0;
+}
+
+static inline void data_node_free(struct data_node *self)
+{
+ unsigned int i;
+
+ switch (self->type) {
+ case DATA_STRING:
+ break;
+ case DATA_HASH:
+ for (i = 0; i < self->hash.elems_used; i++) {
+ data_node_free(self->hash.elems[i].node);
+ free(self->hash.elems[i].id);
+ }
+ break;
+ case DATA_ARRAY:
+ for (i = 0; i < self->array.array_used; i++)
+ data_node_free(self->array.array[i]);
+ break;
+ }
+
+ free(self);
+}
+
+static inline int data_node_hash_del(struct data_node *self, const char *id)
+{
+ unsigned int i;
+ struct data_node_hash *hash = &self->hash;
+
+ for (i = 0; i < hash->elems_used; i++) {
+ if (!strcmp(hash->elems[i].id, id))
+ break;
+ }
+
+ if (i >= hash->elems_used)
+ return 0;
+
+ data_node_free(hash->elems[i].node);
+ free(hash->elems[i].id);
+
+ hash->elems[i] = hash->elems[--hash->elems_used];
+
+ return 1;
+}
+
+static struct data_node *data_node_hash_get(struct data_node *self, const char *id)
+{
+ unsigned int i;
+ struct data_node_hash *hash = &self->hash;
+
+ for (i = 0; i < hash->elems_used; i++) {
+ if (!strcmp(hash->elems[i].id, id))
+ break;
+ }
+
+ if (i >= hash->elems_used)
+ return NULL;
+
+ return hash->elems[i].node;
+}
+
+static inline int data_node_array_add(struct data_node *self, struct data_node *payload)
+{
+ if (self->type != DATA_ARRAY)
+ return 1;
+
+ struct data_node_array *array = &self->array;
+
+ if (array->array_used == array->array_len)
+ return 1;
+
+ array->array[array->array_used++] = payload;
+
+ return 0;
+}
+
+static inline unsigned int data_node_array_len(struct data_node *self)
+{
+ if (self->type != DATA_ARRAY)
+ return 0;
+
+ return self->array.array_used;
+}
+
+static inline void data_print_padd(unsigned int i)
+{
+ while (i-- > 0)
+ putchar(' ');
+}
+
+static inline void data_node_print_(struct data_node *self, unsigned int padd)
+{
+ unsigned int i;
+
+ switch (self->type) {
+ case DATA_STRING:
+ data_print_padd(padd);
+ printf("'%s'\n", self->string.val);
+ break;
+ case DATA_HASH:
+ for (i = 0; i < self->hash.elems_used; i++) {
+ data_print_padd(padd);
+ printf("%s = {\n", self->hash.elems[i].id);
+ data_node_print_(self->hash.elems[i].node, padd+1);
+ data_print_padd(padd);
+ printf("},\n");
+ }
+ break;
+ case DATA_ARRAY:
+ for (i = 0; i < self->array.array_used; i++) {
+ data_print_padd(padd);
+ printf("{\n");
+ data_node_print_(self->array.array[i], padd+1);
+ data_print_padd(padd);
+ printf("},\n");
+ }
+ break;
+ }
+}
+
+static inline void data_node_print(struct data_node *self)
+{
+ printf("{\n");
+ data_node_print_(self, 1);
+ printf("}\n");
+}
+
+static inline void data_fprintf(FILE *f, unsigned int padd, const char *fmt, ...)
+ __attribute__((format (printf, 3, 4)));
+
+static inline void data_fprintf(FILE *f, unsigned int padd, const char *fmt, ...)
+{
+ va_list va;
+
+ while (padd-- > 0)
+ putc(' ', f);
+
+ va_start(va, fmt);
+ vfprintf(f, fmt, va);
+ va_end(va);
+}
+
+static inline void data_to_json_(struct data_node *self, FILE *f, unsigned int padd, int do_padd)
+{
+ unsigned int i;
+
+ switch (self->type) {
+ case DATA_STRING:
+ padd = do_padd ? padd : 0;
+ data_fprintf(f, padd, "\"%s\"", self->string.val);
+ break;
+ case DATA_HASH:
+ for (i = 0; i < self->hash.elems_used; i++) {
+ data_fprintf(f, padd, "\"%s\": ", self->hash.elems[i].id);
+ data_to_json_(self->hash.elems[i].node, f, padd+1, 0);
+ if (i < self->hash.elems_used - 1)
+ fprintf(f, ",\n");
+ else
+ fprintf(f, "\n");
+ }
+ break;
+ case DATA_ARRAY:
+ data_fprintf(f, do_padd ? padd : 0, "[\n");
+ for (i = 0; i < self->array.array_used; i++) {
+ data_to_json_(self->array.array[i], f, padd+1, 1);
+ if (i < self->array.array_used - 1)
+ fprintf(f, ",\n");
+ else
+ fprintf(f, "\n");
+ }
+ data_fprintf(f, padd, "]");
+ break;
+ }
+}
+
+static inline void data_to_json(struct data_node *self, FILE *f, unsigned int padd)
+{
+ fprintf(f, "{\n");
+ data_to_json_(self, f, padd + 1, 1);
+ data_fprintf(f, padd, "}");
+}
+
+#endif /* DATA_STORAGE_H__ */
new file mode 100644
@@ -0,0 +1,423 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2019 Cyril Hrubis <chrubis@suse.cz>
+ * Copyright (c) 2020 Petr Vorel <pvorel@suse.cz>
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <libgen.h>
+#include <ctype.h>
+#include <unistd.h>
+
+#include "data_storage.h"
+
+static void oneline_comment(FILE *f)
+{
+ int c;
+
+ do {
+ c = getc(f);
+ } while (c != '\n');
+}
+
+static const char *eat_asterisk_space(const char *c)
+{
+ unsigned int i = 0;
+
+ while (isspace(c[i]))
+ i++;
+
+ if (c[i] == '*') {
+ while (isspace(c[i+1]))
+ i++;
+ return &c[i+1];
+ }
+
+ return c;
+}
+
+static void multiline_comment(FILE *f, struct data_node *doc)
+{
+ int c;
+ int state = 0;
+ char buf[4096];
+ unsigned int bufp = 0;
+
+ for (;;) {
+ c = getc(f);
+
+ if (doc) {
+ if (c == '\n') {
+ struct data_node *line;
+ buf[bufp] = 0;
+ line = data_node_string(eat_asterisk_space(buf));
+ data_node_array_add(doc, line);
+ bufp = 0;
+ continue;
+ }
+
+ if (bufp + 1 >= sizeof(buf))
+ continue;
+
+ buf[bufp++] = c;
+ }
+
+ switch (state) {
+ case 0:
+ if (c == '*')
+ state = 1;
+ break;
+ case 1:
+ switch (c) {
+ case '/':
+ return;
+ case '*':
+ continue;
+ default:
+ state = 0;
+ break;
+ }
+ break;
+ }
+ }
+
+}
+
+static const char doc_prefix[] = "\\\n";
+
+static void maybe_doc_comment(FILE *f, struct data_node *doc)
+{
+ int c, i;
+
+ for (i = 0; doc_prefix[i]; i++) {
+ c = getc(f);
+
+ if (c == doc_prefix[i])
+ continue;
+
+ if (c == '*')
+ ungetc(c, f);
+
+ multiline_comment(f, NULL);
+ return;
+ }
+
+ multiline_comment(f, doc);
+}
+
+static void maybe_comment(FILE *f, struct data_node *doc)
+{
+ int c = getc(f);
+
+ switch (c) {
+ case '/':
+ oneline_comment(f);
+ break;
+ case '*':
+ maybe_doc_comment(f, doc);
+ break;
+ default:
+ ungetc(c, f);
+ break;
+ }
+}
+
+const char *next_token(FILE *f, struct data_node *doc)
+{
+ size_t i = 0;
+ static char buf[4096];
+ int c;
+ int in_str = 0;
+
+ for (;;) {
+ c = fgetc(f);
+
+ if (c == EOF)
+ goto exit;
+
+ if (in_str) {
+ if (c == '"') {
+ if (i == 0 || buf[i-1] != '\\') {
+ buf[i++] = c;
+ goto exit;
+ }
+ }
+
+ buf[i++] = c;
+ continue;
+ }
+
+ switch (c) {
+ case '{':
+ case '}':
+ case ';':
+ case '(':
+ case ')':
+ case '=':
+ case ',':
+ case '[':
+ case ']':
+ if (i) {
+ ungetc(c, f);
+ goto exit;
+ }
+
+ buf[i++] = c;
+ goto exit;
+ case '0' ... '9':
+ case 'a' ... 'z':
+ case 'A' ... 'Z':
+ case '.':
+ case '_':
+ case '-':
+ buf[i++] = c;
+ break;
+ case '/':
+ maybe_comment(f, doc);
+ break;
+ case '"':
+ in_str = 1;
+ break;
+ case ' ':
+ case '\n':
+ case '\t':
+ if (i)
+ goto exit;
+ break;
+ }
+ }
+
+exit:
+ if (i == 0)
+ return NULL;
+
+ buf[i] = 0;
+ return buf;
+}
+
+#define WARN(str) fprintf(stderr, str "\n")
+
+static int parse_array(FILE *f, struct data_node *node)
+{
+ const char *token;
+
+ for (;;) {
+ if (!(token = next_token(f, NULL)))
+ return 1;
+
+ if (!strcmp(token, "{")) {
+ struct data_node *ret = data_node_array();
+ parse_array(f, ret);
+
+ if (data_node_array_len(ret))
+ data_node_array_add(node, ret);
+ else
+ data_node_free(ret);
+
+ continue;
+ }
+
+ if (!strcmp(token, "}"))
+ return 0;
+
+ if (!strcmp(token, ","))
+ continue;
+
+ if (!strcmp(token, "NULL"))
+ continue;
+
+ struct data_node *str = data_node_string(token);
+
+ data_node_array_add(node, str);
+ }
+
+ return 0;
+}
+
+static int parse_test_struct(FILE *f, struct data_node *doc, struct data_node *node)
+{
+ const char *token;
+ char *id = NULL;
+ int state = 0;
+ struct data_node *ret;
+
+ for (;;) {
+ if (!(token = next_token(f, doc)))
+ return 1;
+
+ if (!strcmp(token, "}"))
+ return 0;
+
+ switch (state) {
+ case 0:
+ id = strdup(token);
+ state = 1;
+ continue;
+ case 1:
+ if (!strcmp(token, "="))
+ state = 2;
+ else
+ WARN("Expected '='");
+ continue;
+ case 2:
+ if (!strcmp(token, "(")) {
+ state = 3;
+ continue;
+ }
+ break;
+ case 3:
+ if (!strcmp(token, ")"))
+ state = 2;
+ continue;
+
+ case 4:
+ if (!strcmp(token, ","))
+ state = 0;
+ continue;
+ }
+
+ if (!strcmp(token, "{")) {
+ ret = data_node_array();
+ parse_array(f, ret);
+ } else {
+ ret = data_node_string(token);
+ }
+
+ const char *key = id;
+ if (key[0] == '.')
+ key++;
+
+ data_node_hash_add(node, key, ret);
+ free(id);
+ state = 4;
+ }
+}
+
+static const char *tokens[] = {
+ "static",
+ "struct",
+ "tst_test",
+ "test",
+ "=",
+ "{",
+};
+
+static struct data_node *parse_file(const char *fname)
+{
+ int state = 0, found = 0;
+ const char *token;
+
+ if (access(fname, F_OK)) {
+ fprintf(stderr, "file %s is not exist\n", fname);
+ return NULL;
+ }
+
+ FILE *f = fopen(fname, "r");
+
+ struct data_node *res = data_node_hash();
+ struct data_node *doc = data_node_array();
+
+ while ((token = next_token(f, doc))) {
+ if (state < 6 && !strcmp(tokens[state], token))
+ state++;
+ else
+ state = 0;
+
+ if (state < 6)
+ continue;
+
+ found = 1;
+ parse_test_struct(f, doc, res);
+ }
+
+
+ if (data_node_array_len(doc)) {
+ data_node_hash_add(res, "doc", doc);
+ found = 1;
+ } else {
+ data_node_free(doc);
+ }
+
+ fclose(f);
+
+ if (!found) {
+ data_node_free(res);
+ return NULL;
+ }
+
+ return res;
+}
+
+static const char *filter_out[] = {
+ "bufs",
+ "cleanup",
+ "mntpoint",
+ "setup",
+ "tcnt",
+ "test",
+ "test_all",
+ NULL
+};
+
+static struct implies {
+ const char *flag;
+ const char *implies;
+} implies[] = {
+ {"format_device", "needs_device"},
+ {"mount_device", "needs_device"},
+ {"mount_device", "format_device"},
+ {"all_filesystems", "needs_device"},
+ {"needs_device", "needs_tmpdir"},
+ {NULL, NULL}
+};
+
+const char *strip_name(char *path)
+{
+ char *name = basename(path);
+ size_t len = strlen(name);
+
+ if (len > 2 && name[len-1] == 'c' && name[len-2] == '.')
+ name[len-2] = '\0';
+
+ return name;
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct data_node *res;
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: docparse filename.c\n");
+ return 1;
+ }
+
+ res = parse_file(argv[1]);
+ if (!res)
+ return 0;
+
+ /* Filter out useless data */
+ for (i = 0; filter_out[i]; i++)
+ data_node_hash_del(res, filter_out[i]);
+
+ /* Normalize the result */
+ for (i = 0; implies[i].flag; i++) {
+ if (!data_node_hash_get(res, implies[i].flag))
+ continue;
+
+ if (data_node_hash_get(res, implies[i].implies)) {
+ fprintf(stderr, "%s: useless tag: %s\n", argv[1], implies[i].implies);
+ continue;
+ }
+
+ data_node_hash_add(res, implies[i].implies, data_node_string("1"));
+ }
+
+ data_node_hash_add(res, "fname", data_node_string(argv[1]));
+ printf(" \"%s\": ", strip_name(argv[1]));
+ data_to_json(res, stdout, 2);
+ data_node_free(res);
+
+ return 0;
+}
new file mode 100755
@@ -0,0 +1,41 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2019 Cyril Hrubis <chrubis@suse.cz>
+# Copyright (c) 2020 Petr Vorel <pvorel@suse.cz>
+set -e
+
+top_builddir=$PWD/..
+top_srcdir="$(dirname $0)/.."
+
+cd $top_srcdir
+
+version=$(cat $top_srcdir/VERSION)
+if [ -d .git ]; then
+ version=$(git describe 2>/dev/null) || version=$(cat $top_srcdir/VERSION).GIT-UNKNOWN
+fi
+
+echo '{'
+echo ' "testsuite": "Linux Test Project",'
+echo ' "testsuite_short": "LTP",'
+echo ' "url": "https://github.com/linux-test-project/ltp/",'
+echo ' "scm_url_base": "https://github.com/linux-test-project/ltp/tree/master/",'
+echo ' "timeout": 300,'
+echo " \"version\": \"$version\","
+echo ' "tests": {'
+
+first=1
+
+for test in `find testcases/ -name '*.c'`; do
+ a=$($top_builddir/docparse/docparse "$test")
+ if [ -n "$a" ]; then
+ if [ -z "$first" ]; then
+ echo ','
+ fi
+ first=
+ echo -n "$a"
+ fi
+done
+
+echo
+echo ' }'
+echo '}'
new file mode 100755
@@ -0,0 +1,40 @@
+#!/usr/bin/perl
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2019 Cyril Hrubis <chrubis@suse.cz>
+
+use strict;
+use warnings;
+
+use JSON;
+use Data::Dumper;
+
+sub load_json
+{
+ my ($fname) = @_;
+ local $/;
+
+ open(my $fh, '<', $fname) or die("Can't open $fname $!");
+
+ return <$fh>;
+}
+
+sub query_flag
+{
+ my ($json, $flag) = @_;
+
+ my $tests = $json->{'tests'};
+
+ foreach my $key (sort(keys %$tests)) {
+ if ($tests->{$key}->{$flag}) {
+ if ($tests->{$key}->{$flag} eq "1") {
+ print("$key\n");
+ } else {
+ print("$key:\n" . Dumper($tests->{$key}->{$flag}) . "\n");
+ }
+ }
+ }
+}
+
+my $json = decode_json(load_json($ARGV[0]));
+
+query_flag($json, $ARGV[1]);