[PATCH v2 2/2] Raise strace awareness

Eugene Syromyatnikov evgsyr at gmail.com
Fri Apr 1 18:24:53 UTC 2022


After des Strausses awareness has been raised sufficiently,
it is time for den Strauss to raise the awareness about strace,
and to do so, the most modern and contemporary method has been elected:
displaying tips, tricks and tweaks on each run.

* src/strace.c (parse_tips_args): New function.
(init) <enum>: Add GETOPT_TIPS.
<longopts>: Add  "tips" option.
(init) <case GETOPT_TIPS>: Call parse_tips_args.
(terminate): Call print_totd before exit.
(usage): Document --tips.
* doc/strace.1.in (.SS Miscellaneous): Ditto.
* straus.c (MAX_TIP_LINES): New enum.
(tips_tricks_tweaks, tip_top, tip_bottom, tip_left, tip_right): New
static constants.
(show_tips, tip_id): New variable.
(print_totd): New function.
* straus.h (tips_fmt, tip_ids): New enumerations.
(show_tips, tip_id, print_totd): New declarations.
* tests/Makefile.am (MISC_TESTS): Add strace--tips.test,
strace--tips-full.test.
(EXTRA_DIST): Add strace--tips.exp.
* tests/strace--tips-full.test: New test.
* tests/strace--tips.test: Ditto.
* tests/strace--tips.exp: New file.
* tests/options-syntax.test: Add --tips syntax checks.
* NEWS: Mention it.

Suggested-by: Elvira Khabirova <lineprinter at altlinux.org>
References: https://github.com/strace/strace/issues/14
---
 NEWS                         |   2 +
 doc/strace.1.in              |  28 ++++
 src/strace.c                 |  78 +++++++++++
 src/strauss.c                | 322 +++++++++++++++++++++++++++++++++++++++++++
 src/strauss.h                |  14 ++
 tests/Makefile.am            |   3 +
 tests/options-syntax.test    |  33 +++++
 tests/strace--tips-full.test |   6 +
 tests/strace--tips.exp       |  36 +++++
 tests/strace--tips.test      |  82 +++++++++++
 10 files changed, 604 insertions(+)
 create mode 100755 tests/strace--tips-full.test
 create mode 100644 tests/strace--tips.exp
 create mode 100755 tests/strace--tips.test

diff --git a/NEWS b/NEWS
index 9ffbc4f..f1d405a 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ Noteworthy changes in release ?.?? (????-??-??)
 
 * Improvements
   * Added an interface of raising des Strausses awareness.
+  * Added --tips option to print strace tips, tricks, and tweaks
+    at the end of the tracing session.
 
 * Bug fixes
 
diff --git a/doc/strace.1.in b/doc/strace.1.in
index 0e8fabd..bcfa39d 100644
--- a/doc/strace.1.in
+++ b/doc/strace.1.in
@@ -1561,6 +1561,34 @@ In cases when seccomp-bpf filter setup failed,
 .B strace
 proceeds as usual and stops traced processes on every system call.
 .TP
+.BR \-\-tips [=[[ id: ] \fIid\fR ],[[ format: ] \fIformat\fR ]]
+Show strace tips, tricks, and tweaks before exit.
+.I id
+can be a non-negative integer number,
+which enables printing of specific tip, trick, or tweak
+(these ID are not guaranteed to be stable),
+or
+.B random
+(the default),
+in which case a random tip is printed.
+.I format
+can be one of the following:
+.RS
+.TP 9
+.B none
+No tip is printed.
+Can be used to override the previous setting.
+.TQ
+.B compact
+Print the tip just big enough to contain all the text.
+.TQ
+.B full
+Print the tip in its full glory.
+.RE
+.IP
+Default is
+.BR id:random,format:compact .
+.TP
 .B \-V
 .TQ
 .B \-\-version
diff --git a/src/strace.c b/src/strace.c
index 60d4038..e060760 100644
--- a/src/strace.c
+++ b/src/strace.c
@@ -482,6 +482,10 @@ Miscellaneous:\n\
   -d, --debug    enable debug output to stderr\n\
   -h, --help     print help message\n\
   --seccomp-bpf  enable seccomp-bpf filtering\n\
+  --tips[=[[id:]ID][,[format:]FORMAT]]\n\
+                 show strace tips, tricks, and tweaks on exit\n\
+     id:         non-negative integer or random; default is random\n\
+     format:     none, compact, full; default is compact\n\
   -V, --version  print version\n\
 "
 /* ancient, no one should use it
@@ -1951,6 +1955,71 @@ parse_ts_arg(const char *in_arg)
 	return 0;
 }
 
+static int
+parse_tips_arg(const char *in_arg)
+{
+	static const char id_pfx[] = "id:";
+	static const char fmt_pfx[] = "format:";
+
+	enum {
+		TOKEN_ID = 1 << 0,
+		TOKEN_FORMAT = 1 << 1,
+	} token_type;
+	int id = tip_id;
+	enum tips_fmt fmt = show_tips == TIPS_NONE ? TIPS_COMPACT : show_tips;
+	char *arg = xstrdup(in_arg);
+	char *saveptr = NULL;
+
+	for (const char *token = strtok_r(arg, ",", &saveptr);
+	     token; token = strtok_r(NULL, ",", &saveptr)) {
+		token_type = TOKEN_ID | TOKEN_FORMAT;
+
+		if (!strncasecmp(token, id_pfx, sizeof(id_pfx) - 1)) {
+			token += sizeof(id_pfx) - 1;
+			token_type = TOKEN_ID;
+		} else if (!strncasecmp(token, fmt_pfx,
+					sizeof(fmt_pfx) - 1)) {
+			token += sizeof(fmt_pfx) - 1;
+			token_type = TOKEN_FORMAT;
+
+		}
+
+		if (token_type & TOKEN_ID) {
+			int ret;
+
+			if (!strcasecmp(token, "random")) {
+				id = TIP_ID_RANDOM;
+				continue;
+			} else if ((ret = string_to_uint(token)) >= 0) {
+				id = ret;
+				continue;
+			}
+		}
+
+		if (token_type & TOKEN_FORMAT) {
+			if (!strcasecmp(token, "none")) {
+				fmt = TIPS_NONE;
+				continue;
+			} else if (!strcasecmp(token, "compact")) {
+				fmt = TIPS_COMPACT;
+				continue;
+			} else if (!strcasecmp(token, "full")) {
+				fmt = TIPS_FULL;
+				continue;
+			}
+		}
+
+		free(arg);
+		return -1;
+	}
+
+	tip_id = id;
+	show_tips = fmt;
+
+	free(arg);
+	return 0;
+}
+
 static void
 remove_from_env(char **env, size_t *env_count, const char *var)
 {
@@ -2161,6 +2230,7 @@ init(int argc, char *argv[])
 		GETOPT_OUTPUT_SEPARATELY,
 		GETOPT_TS,
 		GETOPT_PIDNS_TRANSLATION,
+		GETOPT_TIPS,
 
 		GETOPT_QUAL_TRACE,
 		GETOPT_QUAL_ABBREV,
@@ -2221,6 +2291,7 @@ init(int argc, char *argv[])
 		{ "failed-only",	no_argument,	   0, 'Z' },
 		{ "failing-only",	no_argument,	   0, 'Z' },
 		{ "seccomp-bpf",	no_argument,	   0, GETOPT_SECCOMP },
+		{ "tips",		optional_argument, 0, GETOPT_TIPS },
 
 		{ "trace",	required_argument, 0, GETOPT_QUAL_TRACE },
 		{ "abbrev",	required_argument, 0, GETOPT_QUAL_ABBREV },
@@ -2460,6 +2531,10 @@ init(int argc, char *argv[])
 			qualify_secontext(optarg ? optarg : secontext_qual);
 			break;
 #endif
+		case GETOPT_TIPS:
+			if (parse_tips_arg(optarg ?: ""))
+				error_opt_arg(c, lopt, optarg);
+			break;
 		case GETOPT_QUAL_TRACE:
 			qualify_trace(optarg);
 			break;
@@ -3858,6 +3933,7 @@ terminate(void)
 		/* Child was killed by a signal, mimic that.  */
 		exit_code &= 0xff;
 		signal(exit_code, SIG_DFL);
+		print_totd();
 		GCOV_DUMP;
 		raise(exit_code);
 
@@ -3872,6 +3948,8 @@ terminate(void)
 		   Exit with 128 + signo then.  */
 		exit_code += 128;
 	}
+
+	print_totd();
 	exit(exit_code);
 }
 
diff --git a/src/strauss.c b/src/strauss.c
index e3a240f..a165993 100644
--- a/src/strauss.c
+++ b/src/strauss.c
@@ -9,6 +9,10 @@
 
 #include "defs.h"
 
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/time.h>
+
 #include "strauss.h"
 
 static const char *strauss[] = {
@@ -53,6 +57,277 @@ static const char *strauss[] = {
 
 const size_t strauss_lines = ARRAY_SIZE(strauss);
 
+enum { MAX_TIP_LINES = 13 };
+
+static const char *tips_tricks_tweaks[][MAX_TIP_LINES] = {
+	{ "strace has an extensive manual page",
+	  "that covers all the possible options",
+	  "and contains several useful invocation",
+	  "examples." },
+	{ "You can use -o|COMMAND to redirect strace's",
+	  "output to COMMAND.  This may be useful",
+	  "in cases when there is a redirection",
+	  "in place for the traced program.  Don't",
+	  "forget to escape the pipe character, though,",
+	  "as it is usually interpreted by the shell." },
+	{ "It's possible to display timestamps",
+	  " produced by -r, -t, and -T options",
+	  "with nanosecond precision using their",
+	  "long variants: --relative-timestamps=ns,",
+	  "--absolute-timestamps=ns, and",
+	  "--syscall-times=ns, respectively.", "",
+	  "Why microsecond precision is the default?",
+	  "To preserve the historic output format",
+	  "which was limited by struct timeval",
+	  "precision." },
+	{ "A particular quote from a particular novel",
+	  "by Arthur C. Clarke is printed if an attempt",
+	  "is made to attach to a particular process." },
+	{ "It's possible to tune the environment",
+	  "of the traced process using the -E/--env",
+	  "option:", "",
+	  "    strace -E REMOVE_VAR -E VAR=new_val" },
+#ifdef ENABLE_STACKTRACE
+	{ "You can print a stack trace for each traced",
+	  "call by specifying -k/--stack-traces option.",
+# ifdef USE_DEMANGLE
+	  "It can even demangle symbol names.",
+# endif
+	  },
+#else
+	{ "We wish we could tell you that you can",
+	  "specify -k/--stack-traces option to print",
+	  "stack traces for each traced system call,",
+	  "but, unfortunately, you can't: this strace",
+	  "binary is built without stack tracing",
+	  "support." },
+#endif
+#ifdef ENABLE_SECONTEXT
+	{ "You can print SELinux contexts associated",
+	  "with PIDs, FDs, and paths by specifying",
+	  "--secontext option.  Unless provided",
+	  "with the \"full\" parameter, it prints only",
+	  "SELinux context type, and the \"mismatch\"",
+	  "parameter enables printing of the expected",
+	  "context in case of mismatch, so", "",
+	  "    strace --secontext=full,mismatch", "",
+	  "will show all gory SELinux details." },
+#else
+	{ "We wish we could tell you that you can",
+	  "specify --secontext option to print SELinux",
+	  "context for each PID, FD, and path occurred",
+	  "in the trace, but, unfortunately, you can't:",
+	  "this strace binary is built without SELinux",
+	  "support." },
+#endif
+	{ "Have you ever been bitten by an accidental",
+	  "overwrite of the output file specified",
+	  "in the -o option?  Specify",
+	  "-A/--output-append-mode as well,",
+	  "and this problem will never bite you again!" },
+	{ "strace is about as old as the Linux kernel.",
+	  "It has been originally written for SunOS",
+	  "by Paul Kranenburg in 1991.  The support",
+	  "for all OSes except Linux has been dropped",
+	  "since 2012, though, in strace 4.7." },
+	{ "strace is able to decode netlink messages.",
+	  "It does so automatically for I/O performed",
+	  "on netlink sockets.  Try it yourself:", "",
+	  "    strace -e%network ip a" },
+	{ "Filtered syscalls, errors, and signals can",
+	  "be specified either by name or by number,",
+	  "for example:", "",
+	  "    strace --trace=0,1,2 --signal=2,15 true" },
+	{ "It is possible to specify -r and -t options",
+	  "simultaneously since strace 4.22." },
+	{ "Strace can print only successful syscall",
+	  "invocations when supplied with",
+	  "-z/--successful-only option.  There's also",
+	  "a possibility to filter calls with other",
+	  "statuses, please refer to -e status option",
+	  "documentation." },
+	{ "If you trace a process that uses KVM",
+	  "subsystem, --kvm=vcpu option may be of use:",
+	  "it prints KVM VCPU exit reason.  It requires",
+	  "Linux 4.16+, however." },
+	{ "It is possible to get strace out of your way",
+	  "(in terms of parent/child relationships and",
+	  "signal communication) with -D/--daemonize",
+	  "option.  Another option that may be of use",
+	  "in this case is -I/--interruptible, it",
+	  "restricts the set of signals that interrupt",
+	  "strace." },
+	{ "If strace is too chatty to your taste, you",
+	  "can silence it with -qqq option." },
+	{ "strace prints file paths along with file",
+	  "descriptor numbers when it is invoked with",
+	  "-y/--decode-fds option.",
+	  "When -yy (or --decode-fds=all) is provided,",
+	  "it also prints protocol-specific information",
+	  "for sockets and device numbers for character",
+	  "and block device files." },
+	{ "You can control what columns are shown",
+	  "in the call summary table produced by -c/-C",
+	  "options with -U/--summary-columns option.",
+	  "It is a way to get minimum/maximum call",
+	  "duration printed, for example:", "",
+	  "    strace -c -U name,min-time,max-time ls" },
+	{ "If you feel that syscall duration shown",
+	  "in the call summary table (-c/-C option)",
+	  "is not right, you can try to use -w option",
+	  "(that collects wall clock time,",
+	  "instead of system time), maybe that is what",
+	  "you are looking for." },
+	{ "strace understands -z option since 2002,",
+	  "but it wasn't documented because its",
+	  "implementation was broken.  Only 17 years",
+	  "later, in strace 5.2, it was properly",
+	  "implemented and documented." },
+	{ "If you feel that strace is too slow, you may",
+	  "want to try --seccomp-bpf option, maybe you",
+	  "will feel better." },
+	{ "-v is a shorthand for -e abbrev=none and not",
+	  " for -e verbose=all. It is idiosyncratic,",
+	  "but it is the historic behaviour." },
+	{ "strace uses netlink for printing",
+	  "protocol-specific information about socket",
+	  "descriptors (-yy option)." },
+	{ "strace is able to tamper with tracees'",
+	  "execution by injecting an arbitrary return",
+	  "or error value instead of syscall execution,",
+	  "for example:", "",
+	  "    strace --inject=unlink:retval=0", "",
+	  "will prevent execution of unlink calls, but",
+	  "the traced process will think that the calls",
+	  "have succeeded." },
+	{ "strace's tampering capabilities include",
+	  "injection of arbitrary return/error values,",
+	  "injection of a signal, injection of a delay",
+	  "or data before or after syscall execution." },
+	{ "If you want to see numerical values of named",
+	  "constants, there is an option for that:",
+	  "-X/--const-print-style.  When -Xraw",
+	  "(or --const-print-style=raw) is provided,",
+	  "strace prints just the numerical value",
+	  "of an argument; with -Xverbose, it prints",
+	  "values in both numerical and symbolic form." },
+	{ "getpid syscall is present on all",
+	  "architectures except on Alpha, where getxpid",
+	  "syscall (that returns a pair of PID and PPID",
+	  "in a pair of registers) is used instead.",
+	  "Other two examples of syscalls that utilise",
+	  "two registers for their return values are",
+	  "getxuid and getxgid: they return a pair",
+	  "of real and effective UIDs/GIDs." },
+	{ "There are three syscalls that implement",
+	  "generic \"open file\" task: open, openat,",
+	  "and openat2.  On some (newly supported)",
+	  "architectures, open syscall is not even",
+	  "present.  How to write a robust filtering",
+	  "expression in this case?",
+	  "With the conditional syntax, for example:", "",
+	  "    strace --trace=?open,?openat,?openat2", "",
+	  "You may want to escape question marks, since",
+	  "your shell may interpret them as a path glob",
+	  "expression." },
+	{ "It is possible to use regular expressions",
+	  "for syscall names in the -e trace",
+	  "expression, for example:", "",
+	  "    strace -e trace=/^sched_.*", "",
+	  "will trace all scheduling-related syscalls." },
+	{ "IA-64 (Itanium) uses syscall numbers",
+	  "beginning from 1024, because numbers",
+	  "beginning from 0 were designated for i386",
+	  "compat layer (that has never been",
+	  "upstreamed).  Another example",
+	  "of an architecture with sparse syscall table",
+	  "is MIPS, with parts of it beginning at index",
+	  "0 (SVR4 ABI), 1000 (SysV ABI), 2000",
+	  "(BSD 4.3 ABI), 3000 (POSIX ABI), 4000 (Linux",
+	  "O32 ABI), 5000 (Linux N64 ABI), and 6000",
+	  "(Linux N32 ABI)." },
+	{ "Der Strauss, the strace's project mascot,",
+	  "was conceived in 2017.  It is a brainchild",
+	  "of Vitaly Chaykovsky." },
+	/* https://github.com/strace/strace/issues/14 */
+	{ "Medicinal effects of strace can be achieved",
+	  "by invoking it with the following options:", "",
+	  "    strace -DDDqqq -enone --signal=none" },
+	{ "Historically, supplying -o option to strace",
+	  "led to silencing of messages about tracee",
+	  "attach/detach and personality changes.",
+	  "It can be now overridden with --quiet=none",
+	  "option." },
+	{ "You can avoid tracing of \"other programs\"",
+	  "that are executed by the traced program",
+	  "with -b execve option." },
+	{ "-F option used to be a separate option",
+	  "for following vfork calls." },
+	{ "It is possible to provide multiple PIDs",
+	  "to a single -p option with white space",
+	  "or comma as accepted delimiter, in order",
+	  "to support usage like", "",
+	  "    strace -p \"`pidof PROG`\"",
+	  "or",
+	  "    strace -p \"`pgrep PROG`\"", "",
+	  "pidof uses space as a delimiter, pgrep uses",
+	  "newline." },
+	{ "-n option, that prints syscall numbers,",
+	  "while seemingly quite obvious functionality,",
+	  "was added to strace only in version 5.9,",
+	  "in the year 2020." },
+	{ "Instead of tirelessly specifying",
+	  "architecture- and libc-specific sets",
+	  "of syscalls pertaining specific task each",
+	  "time, one can try to use pre-defined syscall",
+	  "classes.  For example,", "",
+	  "    strace -e%creds", "",
+	  "will trace all syscalls related to accessing",
+	  "and modifying process's user/group IDs",
+	  "and capability sets.  Other pre-defined",
+	  "syscall classes include %clock, %desc,%file,",
+	  "%ipc,%memory, %net,%process, and %signal." },
+	{ "Trying to figure out communication between",
+	  "tracees inside a different PID namespace",
+	  "(in so-called \"containers\", for example)?",
+	  "Try out the --pidns-translation option,",
+	  "it prints PIDs in strace's PID NS when a PID",
+	  "reference from a different PID NS occurs",
+	  "in trace.  It is not enabled by default",
+	  "because there is no sane kernel API",
+	  "to perform PID translation between",
+	  "namespaces, so each such translation",
+	  "requires many reads and ioctls in procfs,",
+	  "which may incur severe performance penalty." },
+	{ "If you don't like the way strace escapes",
+	  "non-printable characters using octal",
+	  "numbers, and don't want to sacrifice",
+	  "readability of the ASCII output with -x/-xx",
+	  "options, you might want to try", "",
+	  "    strace --strings-in-hex=non-ascii-chars", "",
+	  "that will change escape sequences",
+	  "to hexadecimal numbers usage." },
+	{ "-Y option (an alias to --decode-pids=comm)",
+	  "shows comm string associated with the PID." },
+};
+
+static const char tip_top[] =
+	"  ______________________________________________    ";
+static const char tip_bottom[] =
+	" \\______________________________________________/   ";
+static const char *tip_left[] = { " / ", " | "};
+static const char *tip_right[] = {
+	" \\   ",
+	" |   ",
+	" \\   ",
+	"  \\  ",
+	"  _\\ ",
+	" /   ",
+	" |   ", };
+
+enum tips_fmt show_tips = TIPS_NONE;
+int tip_id = TIP_ID_RANDOM;
+
 void
 print_strauss(size_t verbosity)
 {
@@ -64,3 +339,50 @@ print_strauss(size_t verbosity)
 	for (size_t i = 0; i < verbosity; i++)
 		puts(strauss[i]);
 }
+
+void
+print_totd(void)
+{
+	static bool printed = false;
+	const int w = (int) (sizeof(tip_top) - 1 - strlen(tip_left[0])
+				- strlen(tip_right[0]));
+	struct timeval tv;
+	size_t id;
+	size_t i;
+
+	if (printed || show_tips == TIPS_NONE)
+		return;
+
+	if (tip_id == TIP_ID_RANDOM) {
+		gettimeofday(&tv, NULL);
+		srand(tv.tv_sec ^ tv.tv_usec);
+		id = rand();
+	} else {
+		id = tip_id;
+	}
+	id %= ARRAY_SIZE(tips_tricks_tweaks);
+
+	fprintf(stderr, "%s%s\n", tip_top, strauss[1]);
+	fprintf(stderr, "%s%-*s%s%s\n",
+		tip_left[0], w, "", tip_right[0], strauss[2]);
+	for (i = 0; (i < MAX_TIP_LINES) && (tips_tricks_tweaks[id][i] ||
+					    (i < (ARRAY_SIZE(tip_right) - 1)));
+	     i++) {
+		fprintf(stderr, "%s%-*.*s%s%s\n",
+			tip_left[MIN(i + 1, ARRAY_SIZE(tip_left) - 1)],
+			w, w, tips_tricks_tweaks[id][i] ?: "",
+			tip_right[MIN(i + 1, ARRAY_SIZE(tip_right) - 1)],
+			strauss[MIN(3 + i, strauss_lines)]);
+	}
+	fprintf(stderr, "%s%s\n",
+		tip_bottom, strauss[MIN(3 + i, strauss_lines)]);
+	do {
+		fprintf(stderr, "%*s%*s%*s%s\n",
+			(int) strlen(tip_left[0]), "",
+			w, "",
+			(int) strlen(tip_right[0]), "",
+			strauss[MIN(4 + i, strauss_lines)]);
+	} while ((show_tips == TIPS_FULL) && (4 + ++i < strauss_lines));
+
+	printed = true;
+}
diff --git a/src/strauss.h b/src/strauss.h
index fb2e427..43146f7 100644
--- a/src/strauss.h
+++ b/src/strauss.h
@@ -12,8 +12,22 @@
 
 enum { STRAUSS_START_VERBOSITY = 5 };
 
+
+enum tips_fmt {
+	TIPS_NONE,
+	TIPS_COMPACT,
+	TIPS_FULL,
+};
+
+enum tip_ids {
+	TIP_ID_RANDOM = -1,
+};
+
 extern const size_t strauss_lines;
+extern enum tips_fmt show_tips;
+extern int tip_id;
 
 extern void print_strauss(size_t verbosity);
+extern void print_totd(void);
 
 #endif /* STRACE_STRAUSS_H */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index d21230b..3b90d56 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -572,6 +572,8 @@ MISC_TESTS = \
 	status-detached.test \
 	status-none-threads.test \
 	status-unfinished-threads.test \
+	strace--tips.test \
+	strace--tips-full.test \
 	strace-C.test \
 	strace-D.test \
 	strace-DD.test \
@@ -699,6 +701,7 @@ EXTRA_DIST = \
 	strace--syscall-times-ms.expected \
 	strace--syscall-times-us.expected \
 	strace--syscall-times-ns.expected \
+	strace--tips.exp \
 	strace-C.expected \
 	strace-D.expected \
 	strace-DDD.expected \
diff --git a/tests/options-syntax.test b/tests/options-syntax.test
index 22221bf..d918cf8 100755
--- a/tests/options-syntax.test
+++ b/tests/options-syntax.test
@@ -61,6 +61,39 @@ if [ -n "$compiled_with_secontext" ]; then
 		check_e "invalid secontext 'ss'" $opt=!full,mismatch,ss
 	done
 fi
+
+for opt in 'id' \
+	   'id:' \
+	   '-1' \
+	   '2147483648' \
+	   'id:rand' \
+	   'id:randomm' \
+	   '2,3,radomm,4' \
+	   'format' \
+	   'format:' \
+	   'id:format' \
+	   'id:format:full' \
+	   'format:id:0'
+do
+	check_h "invalid --tips argument: '$opt'" --tips="$opt"
+done
+for opt in '--tips' \
+	   '--tips=' \
+	   '--tips=0' \
+	   '--tips=id:0 --tips' \
+	   '--tips=1,id:42' \
+	   '--tips=1 --tips=id:42' \
+	   '--tips=random' \
+	   '--tips --tips=random' \
+	   '--tips=id:random,23' \
+	   '--tips=id:random,23 --tips=random,2,3,id:4,5' \
+	   '--tips=format:none' \
+	   '--tips=format:compact --tips' \
+	   '--tips --tips=format:full,3,none,id:42,compact'
+do
+	check_h "must have PROG [ARGS] or -p PID" $opt
+done
+
 check_h 'PROG [ARGS] must be specified with -D/--daemonize' -D -p $$
 check_h 'PROG [ARGS] must be specified with -D/--daemonize' -DD -p $$
 check_h 'PROG [ARGS] must be specified with -D/--daemonize' -DDD -p $$
diff --git a/tests/strace--tips-full.test b/tests/strace--tips-full.test
new file mode 100755
index 0000000..2228dfc
--- /dev/null
+++ b/tests/strace--tips-full.test
@@ -0,0 +1,6 @@
+#!/bin/sh -efu
+
+# Check --tips=full option.
+TIPS_FULL=1
+export TIPS_FULL
+. ${srcdir=.}/strace--tips.test
diff --git a/tests/strace--tips.exp b/tests/strace--tips.exp
new file mode 100644
index 0000000..c949ece
--- /dev/null
+++ b/tests/strace--tips.exp
@@ -0,0 +1,36 @@
+     ____
+    /    \
+   |-. .-.|
+   (_@)(_@)
+   .---_  \
+  /..   \_/
+  |__.-^ /
+      }  |
+     |   [
+     [  ]
+    ]   |
+    |   [
+    [  ]
+   /   |        __
+  \|   |/     _/ /_
+ \ |   |//___/__/__/_
+\\  \ /  //    -____/_
+//   "   \\      \___.-
+ //     \\  __.----._/_
+/ '/|||\` .-         __>
+[        /         __.-
+[        [           }
+\        \          /
+ "-._____ \.____.--"
+    |  | |  |
+    |  | |  |
+    |  | |  |
+    |  | |  |
+    {  } {  }
+    |  | |  |
+    |  | |  |
+    |  | |  |
+    /  { |  |
+ .-"   / [   -._
+/___/ /   \ \___"-.
+    -"     "-
diff --git a/tests/strace--tips.test b/tests/strace--tips.test
new file mode 100755
index 0000000..eba8b2c
--- /dev/null
+++ b/tests/strace--tips.test
@@ -0,0 +1,82 @@
+#!/bin/sh -efu
+
+# Check --tips option.
+. "${srcdir=.}/init.sh"
+
+: "${TIPS_FULL:=0}"
+RAND_TIPS=5
+MAX_TIPS=500
+
+tips_fmt_opt=""
+[ 1 -ne "$TIPS_FULL" ] || tips_fmt_opt="--tips=full"
+
+
+# Check that random tip is random
+"$STRACE" -qqq -enone $tips_fmt_opt /bin/true 2> "${OUT}.r0"
+i=0
+ret=0
+while [ "$i" -lt "$RAND_TIPS" ]; do
+	i=$((i + 1))
+	"$STRACE" -qqq -enone --tips $tips_fmt_opt /bin/true 2> "${OUT}.r${i}"
+	diff -q "${OUT}.r$((i - 1))" "${OUT}.r${i}" || { ret=1; break; }
+done
+
+[ 0 != "$ret" ] || fail_ "Tips do not seem to be randomised"
+
+# Check that each tip looks sane
+i=0
+while [ "$i" -lt "$MAX_TIPS" ]; do
+	"$STRACE" -qqq -enone $tips_fmt_opt --tips="id:$i" /bin/true 2> "${OUT}.${i}"
+
+	j=0
+	end_seen=0
+	cat ../strace--tips.exp | while read line; do
+		j="$((j + 1))"
+		case "$j" in
+		1) pat='  ______________________________________________         '"$(sed_re_escape "$line")";;
+		2) pat=' /                                              \\        '"$(sed_re_escape "$line")";;
+		3) pat=' | .{44} |   '"$(sed_re_escape "$line")";;
+		4) pat=' | .{44} \   '"$(sed_re_escape "$line")";;
+		5) pat=' | .{44}  \  '"$(sed_re_escape "$line")";;
+		6) pat=' | .{44}  _\ '"$(sed_re_escape "$line")";;
+		7) pat=' | .{44} /   '"$(sed_re_escape "$line")";;
+		*) pat=' | .{44} |   '"$(sed_re_escape "$line")";;
+		esac
+		end=' \______________________________________________/        '"$(sed_re_escape "$line")"
+		btm='                                                         '"$(sed_re_escape "$line")"
+		s=$(tail -n"+$j" "${OUT}.${i}" | head -n1)
+
+		if [ 0 -ne "$end_seen" ]; then
+			printf '%s' "$s" | grep -Eq "$btm" || \
+				fail_ "Can't match line $j of ${OUT}.${i}" \
+					"('$s') against '$btm'"
+		fi
+
+		printf '%s' "$s" | grep -Eqv "$pat" || continue
+		if [ 8 -lt "$j" ]; then
+			printf '%s' "$s" | grep -Eq "$end" || \
+				fail_ "Can't match line $j of ${OUT}.${i}" \
+					"('$s') against neither '$pat' nor '$end'"
+
+			if [ 0 -ne "$TIPS_FULL" ]; then
+				end_seen=1
+			else
+				break
+			fi
+		else
+			fail_ "Can't match line $j of ${OUT}.${i} ('$s')" \
+				"against '$pat'"
+		fi
+	done
+
+	# Finish when come accross the same tip
+	if [ 0 != "$i" ]; then
+		if diff -q "${OUT}.0" "${OUT}.$i"; then
+			break
+		fi
+	fi
+	i="$((i + 1))"
+done
+
+[ "$i" -lt "$MAX_TIPS" ] || \
+	fail_ "Cannot find end of tips after $i iterations"
-- 
2.10.2



More information about the Strace-devel mailing list