CLI: add-trigger: add --capture option to `on-event` condition
authorSimon Marchi <simon.marchi@efficios.com>
Wed, 18 Mar 2020 16:11:18 +0000 (12:11 -0400)
committerJérémie Galarneau <jeremie.galarneau@efficios.com>
Tue, 9 Mar 2021 03:55:55 +0000 (22:55 -0500)
This patch adds a `--capture` option to the `on-event` condition, to
allow specifying the values of payload or context fields to capture.

The filter parser is re-used, as the syntax of the capture expression is
a subset of the filter expression syntax.  Allowed forms are:

  - payload field name: foo
  - context field name: $ctx.foo
  - app-specific field name: $app.foo:bar

After any of these, array indexing can be used.  For example,
`$ctx.foo[2]`.

Change-Id: I6246148634053b32294956d1f7a03f2798fd1d71
Signed-off-by: Simon Marchi <simon.marchi@efficios.com>
Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
Depends-on: lttng-ust: I5a800fc92e588c2a6a0e26282b0ad5f31c044479

src/bin/lttng/commands/add_trigger.c
src/common/filter/filter-ir.h
tests/regression/tools/trigger/test_add_trigger_cli

index 83fd1c4473c437897cf95caf08a49c999cf951f7..19a1c59e76e80a960f95d8223711a91465206aa9 100644 (file)
@@ -19,6 +19,9 @@
 /* For lttng_event_rule_type_str(). */
 #include <lttng/event-rule/event-rule-internal.h>
 #include <lttng/lttng.h>
+#include "common/filter/filter-ast.h"
+#include "common/filter/filter-ir.h"
+#include "common/dynamic-array.h"
 
 #if (LTTNG_SYMBOL_NAME_LEN == 256)
 #define LTTNG_SYMBOL_NAME_LEN_SCANF_IS_A_BROKEN_API "255"
@@ -65,6 +68,8 @@ enum {
        OPT_CTRL_URL,
        OPT_URL,
        OPT_PATH,
+
+       OPT_CAPTURE,
 };
 
 static const struct argpar_opt_descr event_rule_opt_descrs[] = {
@@ -88,6 +93,9 @@ static const struct argpar_opt_descr event_rule_opt_descrs[] = {
        { OPT_SYSCALL, '\0', "syscall" },
        { OPT_TRACEPOINT, '\0', "tracepoint" },
 
+       /* Capture descriptor */
+       { OPT_CAPTURE, '\0', "capture", true },
+
        ARGPAR_OPT_DESCR_SENTINEL
 };
 
@@ -291,9 +299,233 @@ end:
        return ret;
 }
 
-static struct lttng_event_rule *parse_event_rule(int *argc, const char ***argv)
+static
+struct lttng_event_expr *ir_op_load_expr_to_event_expr(
+               const struct ir_load_expression *load_exp, const char *capture_str)
+{
+       char *provider_name = NULL;
+       struct lttng_event_expr *event_expr = NULL;
+       const struct ir_load_expression_op *load_expr_op = load_exp->child;
+
+       switch (load_expr_op->type) {
+       case IR_LOAD_EXPRESSION_GET_PAYLOAD_ROOT:
+       case IR_LOAD_EXPRESSION_GET_CONTEXT_ROOT:
+       {
+               const char *field_name;
+
+               load_expr_op = load_expr_op->next;
+               assert(load_expr_op);
+               assert(load_expr_op->type == IR_LOAD_EXPRESSION_GET_SYMBOL);
+               field_name = load_expr_op->u.symbol;
+               assert(field_name);
+
+               event_expr = load_expr_op->type == IR_LOAD_EXPRESSION_GET_PAYLOAD_ROOT ?
+                               lttng_event_expr_event_payload_field_create(field_name) :
+                               lttng_event_expr_channel_context_field_create(field_name);
+               if (!event_expr) {
+                       ERR("Failed to create %s event expression: field name = `%s`.",
+                                       load_expr_op->type == IR_LOAD_EXPRESSION_GET_PAYLOAD_ROOT ?
+                                                       "payload field" : "channel context",
+                                                       field_name);
+                       goto error;
+               }
+
+               break;
+       }
+       case IR_LOAD_EXPRESSION_GET_APP_CONTEXT_ROOT:
+       {
+               const char *colon;
+               const char *type_name;
+               const char *field_name;
+
+               load_expr_op = load_expr_op->next;
+               assert(load_expr_op);
+               assert(load_expr_op->type == IR_LOAD_EXPRESSION_GET_SYMBOL);
+               field_name = load_expr_op->u.symbol;
+               assert(field_name);
+
+               /*
+                * The field name needs to be of the form PROVIDER:TYPE. We
+                * split it here.
+                */
+               colon = strchr(field_name, ':');
+               if (!colon) {
+                       ERR("Invalid app-specific context field name: missing colon in `%s`.",
+                                       field_name);
+                       goto error;
+               }
+
+               type_name = colon + 1;
+               if (*type_name == '\0') {
+                       ERR("Invalid app-specific context field name: missing type name after colon in `%s`.",
+                                       field_name);
+                       goto error;
+               }
+
+               provider_name = strndup(field_name, colon - field_name);
+               if (!provider_name) {
+                       PERROR("Failed to allocate field name string");
+                       goto error;
+               }
+
+               event_expr = lttng_event_expr_app_specific_context_field_create(
+                               provider_name, type_name);
+               if (!event_expr) {
+                       ERR("Failed to create app-specific context field event expression: provider name = `%s`, type name = `%s`",
+                                       provider_name, type_name);
+                       goto error;
+               }
+
+               break;
+       }
+       default:
+               ERR("%s: unexpected load expr type %d.", __func__,
+                               load_expr_op->type);
+               abort();
+       }
+
+       load_expr_op = load_expr_op->next;
+
+       /* There may be a single array index after that. */
+       if (load_expr_op->type == IR_LOAD_EXPRESSION_GET_INDEX) {
+               struct lttng_event_expr *index_event_expr;
+               const uint64_t index = load_expr_op->u.index;
+
+               index_event_expr = lttng_event_expr_array_field_element_create(event_expr, index);
+               if (!index_event_expr) {
+                       ERR("Failed to create array field element event expression.");
+                       goto error;
+               }
+
+               event_expr = index_event_expr;
+               load_expr_op = load_expr_op->next;
+       }
+
+       switch (load_expr_op->type) {
+       case IR_LOAD_EXPRESSION_LOAD_FIELD:
+               /*
+                * This is what we expect, IR_LOAD_EXPRESSION_LOAD_FIELD is
+                * always found at the end of the chain.
+                */
+               break;
+       case IR_LOAD_EXPRESSION_GET_SYMBOL:
+               ERR("While parsing expression `%s`: Capturing subfields is not supported.",
+                               capture_str);
+               goto error;
+
+       default:
+               ERR("%s: unexpected load expression operator %s.", __func__,
+                               ir_load_expression_type_str(load_expr_op->type));
+               abort();
+       }
+
+       goto end;
+
+error:
+       lttng_event_expr_destroy(event_expr);
+       event_expr = NULL;
+
+end:
+       free(provider_name);
+
+       return event_expr;
+}
+
+static
+struct lttng_event_expr *ir_op_load_to_event_expr(
+               const struct ir_op *ir, const char *capture_str)
+{
+       struct lttng_event_expr *event_expr = NULL;
+
+       assert(ir->op == IR_OP_LOAD);
+
+       switch (ir->data_type) {
+       case IR_DATA_EXPRESSION:
+       {
+               const struct ir_load_expression *ir_load_expr =
+                               ir->u.load.u.expression;
+
+               event_expr = ir_op_load_expr_to_event_expr(
+                               ir_load_expr, capture_str);
+               break;
+       }
+       default:
+               ERR("%s: unexpected data type: %s.", __func__,
+                               ir_data_type_str(ir->data_type));
+               abort();
+       }
+
+       return event_expr;
+}
+
+static
+const char *ir_operator_type_human_str(enum ir_op_type op)
+{
+       const char *name;
+
+       switch (op) {
+       case IR_OP_BINARY:
+               name = "Binary";
+               break;
+       case IR_OP_UNARY:
+               name = "Unary";
+               break;
+       case IR_OP_LOGICAL:
+               name = "Logical";
+               break;
+       default:
+               abort();
+       }
+
+       return name;
+}
+
+static
+struct lttng_event_expr *ir_op_root_to_event_expr(const struct ir_op *ir,
+               const char *capture_str)
+{
+       struct lttng_event_expr *event_expr = NULL;
+
+       assert(ir->op == IR_OP_ROOT);
+       ir = ir->u.root.child;
+
+       switch (ir->op) {
+       case IR_OP_LOAD:
+               event_expr = ir_op_load_to_event_expr(ir, capture_str);
+               break;
+       case IR_OP_BINARY:
+       case IR_OP_UNARY:
+       case IR_OP_LOGICAL:
+               ERR("While parsing expression `%s`: %s operators are not allowed in capture expressions.",
+                               capture_str,
+                               ir_operator_type_human_str(ir->op));
+               break;
+       default:
+               ERR("%s: unexpected IR op type: %s.", __func__,
+                               ir_op_type_str(ir->op));
+               abort();
+       }
+
+       return event_expr;
+}
+
+static
+void destroy_event_expr(void *ptr)
+{
+       lttng_event_expr_destroy(ptr);
+}
+
+struct parse_event_rule_res {
+       /* Owned by this. */
+       struct lttng_event_rule *er;
+
+       /* Array of `struct lttng_event_expr *` */
+       struct lttng_dynamic_pointer_array capture_descriptors;
+};
+
+static
+struct parse_event_rule_res parse_event_rule(int *argc, const char ***argv)
 {
-       struct lttng_event_rule *er = NULL;
        enum lttng_domain_type domain_type = LTTNG_DOMAIN_NONE;
        enum lttng_event_rule_type event_rule_type =
                        LTTNG_EVENT_RULE_TYPE_UNKNOWN;
@@ -303,6 +535,9 @@ static struct lttng_event_rule *parse_event_rule(int *argc, const char ***argv)
        int consumed_args = -1;
        struct lttng_kernel_probe_location *kernel_probe_location = NULL;
        struct lttng_userspace_probe_location *userspace_probe_location = NULL;
+       struct parse_event_rule_res res = { 0 };
+       struct lttng_event_expr *event_expr = NULL;
+       struct filter_parser_ctx *parser_ctx = NULL;
 
        /* Was the -a/--all flag provided? */
        bool all_events = false;
@@ -324,6 +559,8 @@ static struct lttng_event_rule *parse_event_rule(int *argc, const char ***argv)
        char *loglevel_str = NULL;
        bool loglevel_only = false;
 
+       lttng_dynamic_pointer_array_init(&res.capture_descriptors,
+                               destroy_event_expr);
        state = argpar_state_create(*argc, *argv, event_rule_opt_descrs);
        if (!state) {
                ERR("Failed to allocate an argpar state.");
@@ -455,6 +692,45 @@ static struct lttng_event_rule *parse_event_rule(int *argc, const char ***argv)
                                loglevel_only = item_opt->descr->id ==
                                                OPT_LOGLEVEL_ONLY;
                                break;
+                       case OPT_CAPTURE:
+                       {
+                               int ret;
+                               const char *capture_str = item_opt->arg;
+
+                               ret = filter_parser_ctx_create_from_filter_expression(
+                                               capture_str, &parser_ctx);
+                               if (ret) {
+                                       ERR("Failed to parse capture expression `%s`.",
+                                                       capture_str);
+                                       goto error;
+                               }
+
+                               event_expr = ir_op_root_to_event_expr(
+                                               parser_ctx->ir_root,
+                                               capture_str);
+                               if (!event_expr) {
+                                       /*
+                                        * ir_op_root_to_event_expr has printed
+                                        * an error message.
+                                        */
+                                       goto error;
+                               }
+
+                               ret = lttng_dynamic_pointer_array_add_pointer(
+                                               &res.capture_descriptors,
+                                               event_expr);
+                               if (ret) {
+                                       goto error;
+                               }
+
+                               /*
+                                * The ownership of event expression was
+                                * transferred to the dynamic array.
+                                */
+                               event_expr = NULL;
+
+                               break;
+                       }
                        default:
                                abort();
                        }
@@ -599,15 +875,15 @@ static struct lttng_event_rule *parse_event_rule(int *argc, const char ***argv)
        {
                enum lttng_event_rule_status event_rule_status;
 
-               er = lttng_event_rule_tracepoint_create(domain_type);
-               if (!er) {
+               res.er = lttng_event_rule_tracepoint_create(domain_type);
+               if (!res.er) {
                        ERR("Failed to create tracepoint event rule.");
                        goto error;
                }
 
                /* Set pattern. */
                event_rule_status = lttng_event_rule_tracepoint_set_pattern(
-                               er, tracepoint_name);
+                               res.er, tracepoint_name);
                if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) {
                        ERR("Failed to set tracepoint event rule's pattern to '%s'.",
                                        tracepoint_name);
@@ -617,7 +893,7 @@ static struct lttng_event_rule *parse_event_rule(int *argc, const char ***argv)
                /* Set filter. */
                if (filter) {
                        event_rule_status = lttng_event_rule_tracepoint_set_filter(
-                                       er, filter);
+                                       res.er, filter);
                        if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) {
                                ERR("Failed to set tracepoint event rule's filter to '%s'.",
                                                filter);
@@ -631,7 +907,7 @@ static struct lttng_event_rule *parse_event_rule(int *argc, const char ***argv)
 
                        for (n = 0; exclusion_list[n]; n++) {
                                event_rule_status = lttng_event_rule_tracepoint_add_exclusion(
-                                               er,
+                                               res.er,
                                                exclusion_list[n]);
                                if (event_rule_status !=
                                                LTTNG_EVENT_RULE_STATUS_OK) {
@@ -660,10 +936,12 @@ static struct lttng_event_rule *parse_event_rule(int *argc, const char ***argv)
 
                        if (loglevel_only) {
                                event_rule_status = lttng_event_rule_tracepoint_set_log_level(
-                                               er, loglevel);
+                                               res.er,
+                                               loglevel);
                        } else {
                                event_rule_status = lttng_event_rule_tracepoint_set_log_level_range_lower_bound(
-                                               er, loglevel);
+                                               res.er,
+                                               loglevel);
                        }
 
                        if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) {
@@ -679,8 +957,8 @@ static struct lttng_event_rule *parse_event_rule(int *argc, const char ***argv)
                int ret;
                enum lttng_event_rule_status event_rule_status;
 
-               er = lttng_event_rule_kprobe_create();
-               if (!er) {
+               res.er = lttng_event_rule_kprobe_create();
+               if (!res.er) {
                        ERR("Failed to create kprobe event rule.");
                        goto error;
                }
@@ -691,14 +969,14 @@ static struct lttng_event_rule *parse_event_rule(int *argc, const char ***argv)
                        goto error;
                }
 
-               event_rule_status = lttng_event_rule_kprobe_set_name(er, tracepoint_name);
+               event_rule_status = lttng_event_rule_kprobe_set_name(res.er, tracepoint_name);
                if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) {
                        ERR("Failed to set kprobe event rule's name to '%s'.", tracepoint_name);
                        goto error;
                }
 
                assert(kernel_probe_location);
-               event_rule_status = lttng_event_rule_kprobe_set_location(er, kernel_probe_location);
+               event_rule_status = lttng_event_rule_kprobe_set_location(res.er, kernel_probe_location);
                if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) {
                        ERR("Failed to set kprobe event rule's location.");
                        goto error;
@@ -718,21 +996,21 @@ static struct lttng_event_rule *parse_event_rule(int *argc, const char ***argv)
                        goto error;
                }
 
-               er = lttng_event_rule_uprobe_create();
-               if (!er) {
-                       ERR("Failed to create user space probe event rule.");
+               res.er = lttng_event_rule_uprobe_create();
+               if (!res.er) {
+                       ERR("Failed to create userspace probe event rule.");
                        goto error;
                }
 
                event_rule_status = lttng_event_rule_uprobe_set_location(
-                               er, userspace_probe_location);
+                               res.er, userspace_probe_location);
                if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) {
                        ERR("Failed to set user space probe event rule's location.");
                        goto error;
                }
 
                event_rule_status = lttng_event_rule_uprobe_set_name(
-                               er, tracepoint_name);
+                               res.er, tracepoint_name);
                if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) {
                        ERR("Failed to set user space probe event rule's name to '%s'.",
                                        tracepoint_name);
@@ -745,14 +1023,14 @@ static struct lttng_event_rule *parse_event_rule(int *argc, const char ***argv)
        {
                enum lttng_event_rule_status event_rule_status;
 
-               er = lttng_event_rule_syscall_create();
-               if (!er) {
+               res.er = lttng_event_rule_syscall_create();
+               if (!res.er) {
                        ERR("Failed to create syscall event rule.");
                        goto error;
                }
 
                event_rule_status = lttng_event_rule_syscall_set_pattern(
-                               er, tracepoint_name);
+                               res.er, tracepoint_name);
                if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) {
                        ERR("Failed to set syscall event rule's pattern to '%s'.",
                                        tracepoint_name);
@@ -761,7 +1039,7 @@ static struct lttng_event_rule *parse_event_rule(int *argc, const char ***argv)
 
                if (filter) {
                        event_rule_status = lttng_event_rule_syscall_set_filter(
-                                       er, filter);
+                                       res.er, filter);
                        if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) {
                                ERR("Failed to set syscall event rule's filter to '%s'.",
                                                filter);
@@ -779,10 +1057,16 @@ static struct lttng_event_rule *parse_event_rule(int *argc, const char ***argv)
        goto end;
 
 error:
-       lttng_event_rule_destroy(er);
-       er = NULL;
+       lttng_event_rule_destroy(res.er);
+       res.er = NULL;
+       lttng_dynamic_pointer_array_reset(&res.capture_descriptors);
 
 end:
+       if (parser_ctx) {
+               filter_parser_ctx_free(parser_ctx);
+       }
+
+       lttng_event_expr_destroy(event_expr);
        argpar_item_destroy(item);
        free(error);
        argpar_state_destroy(state);
@@ -793,28 +1077,57 @@ end:
        strutils_free_null_terminated_array_of_strings(exclusion_list);
        lttng_kernel_probe_location_destroy(kernel_probe_location);
        lttng_userspace_probe_location_destroy(userspace_probe_location);
-       return er;
+       return res;
 }
 
 static
 struct lttng_condition *handle_condition_event(int *argc, const char ***argv)
 {
-       struct lttng_event_rule *er;
+       struct parse_event_rule_res res;
        struct lttng_condition *c;
+       size_t i;
 
-       er = parse_event_rule(argc, argv);
-       if (!er) {
+       res = parse_event_rule(argc, argv);
+       if (!res.er) {
                c = NULL;
-               goto end;
+               goto error;
        }
 
-       c = lttng_condition_event_rule_create(er);
-       lttng_event_rule_destroy(er);
+       c = lttng_condition_event_rule_create(res.er);
+       lttng_event_rule_destroy(res.er);
+       res.er = NULL;
        if (!c) {
-               goto end;
+               goto error;
        }
 
+       for (i = 0; i < lttng_dynamic_pointer_array_get_count(&res.capture_descriptors);
+                       i++) {
+               enum lttng_condition_status status;
+               struct lttng_event_expr **expr =
+                               lttng_dynamic_array_get_element(
+                                       &res.capture_descriptors.array, i);
+
+               assert(expr);
+               assert(*expr);
+               status = lttng_condition_event_rule_append_capture_descriptor(
+                               c, *expr);
+               if (status != LTTNG_CONDITION_STATUS_OK) {
+                       goto error;
+               }
+
+               /* Ownership of event expression moved to `c` */
+               *expr = NULL;
+       }
+
+       goto end;
+
+error:
+       lttng_condition_destroy(c);
+       c = NULL;
+
 end:
+       lttng_dynamic_pointer_array_reset(&res.capture_descriptors);
+       lttng_event_rule_destroy(res.er);
        return c;
 }
 
index d62c0ee0c4f4d66b5be2336b6104c4f7ce75d4d7..5775e8004571cf007cda4d2b2050d366f5f06c24 100644 (file)
@@ -31,6 +31,29 @@ enum ir_data_type {
        IR_DATA_EXPRESSION,
 };
 
+static inline
+const char *ir_data_type_str(enum ir_data_type type)
+{
+       switch (type) {
+       case IR_DATA_UNKNOWN:
+               return "IR_DATA_UNKNOWN";
+       case IR_DATA_STRING:
+               return "IR_DATA_STRING";
+       case IR_DATA_NUMERIC:
+               return "IR_DATA_NUMERIC";
+       case IR_DATA_FLOAT:
+               return "IR_DATA_FLOAT";
+       case IR_DATA_FIELD_REF:
+               return "IR_DATA_FIELD_REF";
+       case IR_DATA_GET_CONTEXT_REF:
+               return "IR_DATA_GET_CONTEXT_REF";
+       case IR_DATA_EXPRESSION:
+               return "IR_DATA_EXPRESSION";
+       default:
+               abort();
+       }
+}
+
 enum ir_op_type {
        IR_OP_UNKNOWN = 0,
        IR_OP_ROOT,
@@ -40,6 +63,27 @@ enum ir_op_type {
        IR_OP_LOGICAL,
 };
 
+static inline
+const char *ir_op_type_str(enum ir_op_type type)
+{
+       switch (type) {
+       case IR_OP_UNKNOWN:
+               return "IR_OP_UNKNOWN";
+       case IR_OP_ROOT:
+               return "IR_OP_ROOT";
+       case IR_OP_LOAD:
+               return "IR_OP_LOAD";
+       case IR_OP_UNARY:
+               return "IR_OP_UNARY";
+       case IR_OP_BINARY:
+               return "IR_OP_BINARY";
+       case IR_OP_LOGICAL:
+               return "IR_OP_LOGICAL";
+       default:
+               abort();
+       }
+}
+
 /* left or right child */
 enum ir_side {
        IR_SIDE_UNKNOWN = 0,
@@ -71,6 +115,27 @@ enum ir_load_expression_type {
        IR_LOAD_EXPRESSION_LOAD_FIELD,
 };
 
+static inline
+const char *ir_load_expression_type_str(enum ir_load_expression_type type)
+{
+       switch (type) {
+       case IR_LOAD_EXPRESSION_GET_CONTEXT_ROOT:
+               return "IR_LOAD_EXPRESSION_GET_CONTEXT_ROOT";
+       case IR_LOAD_EXPRESSION_GET_APP_CONTEXT_ROOT:
+               return "IR_LOAD_EXPRESSION_GET_APP_CONTEXT_ROOT";
+       case IR_LOAD_EXPRESSION_GET_PAYLOAD_ROOT:
+               return "IR_LOAD_EXPRESSION_GET_PAYLOAD_ROOT";
+       case IR_LOAD_EXPRESSION_GET_SYMBOL:
+               return "IR_LOAD_EXPRESSION_GET_SYMBOL";
+       case IR_LOAD_EXPRESSION_GET_INDEX:
+               return "IR_LOAD_EXPRESSION_GET_INDEX";
+       case IR_LOAD_EXPRESSION_LOAD_FIELD:
+               return "IR_LOAD_EXPRESSION_LOAD_FIELD";
+       default:
+               abort();
+       }
+}
+
 struct ir_load_expression_op {
        struct ir_load_expression_op *next;
        enum ir_load_expression_type type;
index acaf456318960a0a3384ebb9f1bec003103087dd..db325a90d61884225eab325f1de7c9a211c500c4 100755 (executable)
@@ -23,7 +23,7 @@ TESTDIR="$CURDIR/../../.."
 # shellcheck source=../../../utils/utils.sh
 source "$TESTDIR/utils/utils.sh"
 
-plan_tests 174
+plan_tests 222
 
 FULL_LTTNG_BIN="${TESTDIR}/../src/bin/lttng/${LTTNG_BIN}"
 
@@ -158,6 +158,34 @@ test_success "--action notify" \
        --condition on-event some-event-notify -u \
        --action notify
 
+test_success "--action notify --capture foo" \
+       --condition on-event some-event-notify-foo -u \
+       --capture foo --action notify
+
+test_success "--action notify --capture foo[2]" \
+       --condition on-event some-event-notify-foo2 -u \
+       --capture 'foo[2]' --action notify
+
+test_success '--action notify --capture $ctx.foo' \
+       --condition on-event some-event-notify-ctx-foo -u \
+       --capture '$ctx.foo' --action notify
+
+test_success '--action notify --capture $ctx.foo[2]' \
+       --condition on-event some-event-notify-ctx-foo2 -u \
+       --capture '$ctx.foo[2]' --action notify
+
+test_success '--action notify --capture $app.prov:type' \
+       --condition on-event some-event-notify-app-prov-type -u \
+       --capture '$app.prov:type' --action notify
+
+test_success '--action notify --capture $app.prov:type[2]' \
+       --condition on-event some-event-notify-app-prov-type-2 -u \
+       --capture '$app.prov:type[2]' --action notify
+
+test_success '--action notify multiple captures' \
+       --condition on-event some-event-notify-multiple-captures -u \
+       --capture foo --capture '$app.hello:world' --action notify
+
 # `--action start-session` successes
 test_success "--action start-session" \
        --condition on-event some-event-start-session -u \
@@ -273,6 +301,51 @@ test_failure "--condition on-event: both -a and a tracepoint name with --syscall
        "Error: Can't provide a tracepoint name with -a/--all." \
        --condition on-event -k --syscall -a open
 
+test_failure "--condition on-event --capture: missing argument (end of arg list)" \
+       'Error: While parsing argument #3 (`--capture`): Missing required argument for option `--capture`' \
+       --action notify \
+       --condition on-event -u -a --capture
+
+test_failure "--condition on-event --capture: missing argument (before another option)" \
+       'Error: While parsing expression `--action`: Unary operators are not allowed in capture expressions.' \
+       --condition on-event -u -a --capture \
+       --action notify \
+
+test_failure "--condition on-event --capture: binary operator" \
+       'Error: While parsing expression `foo == 2`: Binary operators are not allowed in capture expressions.' \
+       --condition on-event -u -a \
+       --capture 'foo == 2' --action notify
+
+test_failure "--condition on-event --capture: unary operator" \
+       'Error: While parsing expression `!foo`: Unary operators are not allowed in capture expressions.' \
+       --condition on-event -u -a \
+       --capture '!foo' --action notify
+
+test_failure "--condition on-event --capture: logical operator" \
+       'Error: While parsing expression `foo || bar`: Logical operators are not allowed in capture expressions.' \
+       --condition on-event -u -a \
+       --capture 'foo || bar' --action notify
+
+test_failure "--condition on-event --capture: accessing a sub-field" \
+       'Error: While parsing expression `foo.bar`: Capturing subfields is not supported.' \
+       --condition on-event -u -a \
+       --capture 'foo.bar' --action notify
+
+test_failure "--condition on-event --capture: accessing the sub-field of an array element" \
+       'Error: While parsing expression `foo[3].bar`: Capturing subfields is not supported.' \
+       --condition on-event -u -a \
+       --capture 'foo[3].bar' --action notify
+
+test_failure "--condition on-event --capture: missing colon in app-specific context field" \
+       'Error: Invalid app-specific context field name: missing colon in `foo`.' \
+       --condition on-event -u -a \
+       --capture '$app.foo' --action notify
+
+test_failure "--condition on-event --capture: missing colon in app-specific context field" \
+       'Error: Invalid app-specific context field name: missing type name after colon in `foo:`.' \
+       --condition on-event -u -a \
+       --capture '$app.foo:' --action notify
+
 # `--action` failures
 test_failure "missing args after --action" \
        "Error: Missing action name." \
This page took 0.03512 seconds and 4 git commands to generate.