TESTS_PLUGINS += plugins/flt.utils.trimmer/test-trimming.sh \
plugins/flt.utils.muxer/succeed/test-succeed.sh \
- plugins/sink.text.pretty/test-enum.sh
+ plugins/sink.text.pretty/test-enum.sh \
+ plugins/src.ctf.fs/field/test-field.sh
endif
endif
--- /dev/null
+# SPDX-License-Identifier: MIT
+#
+# Copyright (c) 2023 Olivier Dion <odion@efficios.com>
+# Copyright (c) 2024 Philippe Proulx <pproulx@efficios.com>
+
+import bt2
+
+_array_elem = object()
+
+
+# Recursively prints the contents of `field` with the indentation level
+# `indent` and the introduction `intro`.
+#
+# `intro` is one of:
+#
+# `None`:
+# No introduction (root field).
+#
+# A string:
+# Structure field member name.
+#
+# `_array_elem`:
+# `field` is an array field element.
+def _print_field(intro, field, indent=0):
+ indent_str = " " * indent * 2
+ intro_str = ""
+
+ if intro is _array_elem:
+ intro_str = "- "
+ elif intro is not None:
+ intro_str = "{}: ".format(intro)
+
+ if isinstance(field, bt2._StringFieldConst):
+ print('{}{}"{}"'.format(indent_str, intro_str, field))
+ elif isinstance(field, bt2._StructureFieldConst):
+ print(indent_str + intro_str, end="")
+
+ if len(field) == 0:
+ # Special case for an empty structure field
+ print("{}")
+ else:
+ if intro is _array_elem:
+ # Structure field is an array field element itself:
+ # print the first element first, and then print the
+ # remaining ones indented.
+ #
+ # Example:
+ #
+ # - meow: "mix"
+ # montant: -23.599312
+ # bateau: "jacques"
+ sub_field_names = list(field)
+ _print_field(sub_field_names[0], field[sub_field_names[0]], 0)
+
+ for sub_field_name in sub_field_names[1:]:
+ _print_field(sub_field_name, field[sub_field_name], indent + 1)
+ else:
+ add_indent = 0
+
+ if intro is not None:
+ # Structure field has a name (already printed at
+ # this point): print a newline, and then print all
+ # the members indented (one more level):
+ #
+ # Example:
+ #
+ # struct_name:
+ # meow: "mix"
+ # montant: -23.599312
+ # bateau: "jacques"
+ add_indent = 1
+ print()
+
+ for sub_field_name in field:
+ _print_field(
+ sub_field_name,
+ field[sub_field_name],
+ indent + add_indent,
+ )
+ elif isinstance(field, bt2._ArrayFieldConst):
+ add_indent = 0
+
+ if intro is not None:
+ # Array field has an intro: print it, then print a newline,
+ # and then print all the elements indented (one more level).
+ #
+ # Example 1 (parent is an structure field):
+ #
+ # array_name:
+ # - -17
+ # - "salut"
+ # - 23
+ #
+ # Example 2 (parent is an array field):
+ #
+ # -
+ # - -17
+ # - "salut"
+ # - 23
+ add_indent = 1
+ print(indent_str + intro_str.rstrip())
+
+ for sub_field in field:
+ _print_field(_array_elem, sub_field, indent + add_indent)
+ elif isinstance(field.cls, bt2._IntegerFieldClassConst):
+ # Honor the preferred display base
+ base = field.cls.preferred_display_base
+ print(indent_str + intro_str, end="")
+
+ if base == 10:
+ print(int(field))
+ elif base == 16:
+ print(hex(field))
+ elif base == 8:
+ print(oct(field))
+ elif base == 2:
+ print(bin(field))
+ elif isinstance(field, bt2._BitArrayFieldConst):
+ print(indent_str + intro_str + bin(field))
+ elif isinstance(field, bt2._BoolFieldConst):
+ print(indent_str + intro_str + ("yes" if field else "no"))
+ elif isinstance(field, bt2._RealFieldConst):
+ print("{}{}{:.6f}".format(indent_str, intro_str, float(field)))
+ elif isinstance(field, bt2._OptionFieldConst):
+ if field.has_field:
+ _print_field(intro, field.field, indent)
+ else:
+ # Special case for an option field without a field
+ print("{}{}~".format(indent_str, intro_str))
+ elif isinstance(field, bt2._VariantFieldConst):
+ _print_field(intro, field.selected_option, indent)
+ else:
+ print(indent_str + intro_str + field)
+
+
+@bt2.plugin_component_class
+class _SingleSinkComponent(bt2._UserSinkComponent, name="single"):
+ def __init__(self, config, params, obj):
+ self._input = self._add_input_port("input")
+ self._field_name = str(params.get("field-name", "root"))
+
+ def _user_graph_is_configured(self):
+ self._it = self._create_message_iterator(self._input)
+
+ def _user_consume(self):
+ msg = next(self._it)
+
+ if type(msg) is bt2._EventMessageConst:
+ assert self._field_name in msg.event.payload_field
+ assert len(msg.event.payload_field) == 1
+ _print_field(None, msg.event.payload_field[self._field_name])
+
+
+bt2.register_plugin(__name__, "test-text")
--- /dev/null
+---
+u32be
+
+---
+[3187239923:32be]
+
+---
+3187239923
--- /dev/null
+---
+u32le
+
+---
+[3187239923:32le]
+
+---
+3187239923
--- /dev/null
+---
+struct {
+ struct {
+ u8 a;
+ nt_str b;
+ } x[3];
+} @[2]
+
+---
+01 # `a`
+"salut\0" # `b`
+
+02 # `a`
+"patente\0" # `b`
+
+03 # `a`
+"Quebec\0" # `b`
+
+04 # `a`
+"chez nous\0" # `b`
+
+05 # `a`
+"aidez-moi\0" # `b`
+
+06 # `a`
+"rasseye\0" # `b`
+
+---
+- x:
+ - a: 1
+ b: "salut"
+ - a: 2
+ b: "patente"
+ - a: 3
+ b: "Quebec"
+- x:
+ - a: 4
+ b: "chez nous"
+ - a: 5
+ b: "aidez-moi"
+ - a: 6
+ b: "rasseye"
--- /dev/null
+---
+struct {
+ u8 a;
+
+ struct {
+ } b;
+
+ u8 c;
+}
+---
+01 # `a`
+02 # `c`
+
+---
+a: 1
+b: {}
+c: 2
--- /dev/null
+---
+struct {
+ i16 meow;
+ nt_str mix;
+ flt32 cat;
+}
+
+---
+[-1717:16] # `meow`
+"rapidement!\0" # `mix`
+[2.897771955:32] # `cat`
+
+---
+meow: -1717
+mix: "rapidement!"
+cat: 2.897772
--- /dev/null
+---
+struct {
+ enum : u8 {
+ MEOW,
+ MIX,
+ } tag;
+
+ variant <tag> {
+ u16 MEOW;
+ nt_str MIX;
+ } var;
+} @[2]
+
+---
+00 # `tag`
+[1995:16] # `var`
+
+01 # `tag`
+"hello there!\0" # `var`
+---
+- tag: 0
+ var: 1995
+- tag: 1
+ var: "hello there!"
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Copyright (C) 2023 EfficiOS Inc.
+#
+# pyright: strict, reportTypeCommentUsage=false
+
+import os
+import string
+import argparse
+
+import normand
+import moultipart
+
+
+def _make_ctf_1_metadata(payload_fc: str):
+ payload_fc = payload_fc.strip()
+
+ if "@" in payload_fc:
+ payload_fc = payload_fc.replace("@", "root")
+ else:
+ payload_fc += " root"
+
+ return string.Template(
+ """\
+/* CTF 1.8 */
+
+trace {
+ major = 1;
+ minor = 8;
+ byte_order = le;
+};
+
+typealias integer { size = 8; } := u8;
+typealias integer { size = 16; } := u16;
+typealias integer { size = 32; } := u32;
+typealias integer { size = 64; } := u64;
+typealias integer { size = 8; byte_order = le; } := u8le;
+typealias integer { size = 16; byte_order = le; } := u16le;
+typealias integer { size = 32; byte_order = le; } := u32le;
+typealias integer { size = 64; byte_order = le; } := u64le;
+typealias integer { size = 8; byte_order = be; } := u8be;
+typealias integer { size = 16; byte_order = be; } := u16be;
+typealias integer { size = 32; byte_order = be; } := u32be;
+typealias integer { size = 64; byte_order = be; } := u64be;
+typealias integer { signed = true; size = 8; } := i8;
+typealias integer { signed = true; size = 16; } := i16;
+typealias integer { signed = true; size = 32; } := i32;
+typealias integer { signed = true; size = 64; } := i64;
+typealias integer { signed = true; size = 8; byte_order = le; } := i8le;
+typealias integer { signed = true; size = 16; byte_order = le; } := i16le;
+typealias integer { signed = true; size = 32; byte_order = le; } := i32le;
+typealias integer { signed = true; size = 64; byte_order = le; } := i64le;
+typealias integer { signed = true; size = 8; byte_order = be; } := i8be;
+typealias integer { signed = true; size = 16; byte_order = be; } := i16be;
+typealias integer { signed = true; size = 32; byte_order = be; } := i32be;
+typealias integer { signed = true; size = 64; byte_order = be; } := i64be;
+typealias floating_point { exp_dig = 8; mant_dig = 24; } := flt32;
+typealias floating_point { exp_dig = 11; mant_dig = 53; } := flt64;
+typealias floating_point { exp_dig = 8; mant_dig = 24; byte_order = le; } := flt32le;
+typealias floating_point { exp_dig = 11; mant_dig = 53; byte_order = le; } := flt64le;
+typealias floating_point { exp_dig = 8; mant_dig = 24; byte_order = be; } := flt32be;
+typealias floating_point { exp_dig = 11; mant_dig = 53; byte_order = be; } := flt64be;
+typealias string { encoding = UTF8; } := nt_str;
+
+event {
+ name = the_event;
+ fields := struct {
+ ${payload_fc};
+ };
+};
+"""
+ ).substitute(payload_fc=payload_fc)
+
+
+def _make_ctf_1_data(normand_text: str):
+ # Default to little-endian because that's also the TSDL default in
+ # _make_ctf_1_metadata() above.
+ return normand.parse("!le\n" + normand_text).data
+
+
+def _create_files_from_mp(mp_path: str, output_dir: str):
+ trace_dir = os.path.join(output_dir, "trace")
+ expect_path = os.path.join(output_dir, "expect")
+ metadata_path = os.path.join(trace_dir, "metadata")
+ data_path = os.path.join(trace_dir, "data")
+ os.makedirs(trace_dir, exist_ok=True)
+
+ with open(mp_path, "r") as f:
+ parts = moultipart.parse(f)
+
+ with open(metadata_path, "w") as f:
+ f.write(_make_ctf_1_metadata(parts[0].content))
+
+ with open(data_path, "wb") as f:
+ f.write(_make_ctf_1_data(parts[1].content))
+
+ with open(expect_path, "w") as f:
+ f.write(parts[2].content)
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "mp_path", metavar="MP-PATH", help="moultipart document to process"
+ )
+ parser.add_argument(
+ "output_dir",
+ metavar="OUTPUT-DIR",
+ help="output directory for the CTF trace and expectation file",
+ )
+ args = parser.parse_args()
+ _create_files_from_mp(args.mp_path, args.output_dir)
query/test_query_support_info.py \
query/test-query-trace-info.sh \
query/test_query_trace_info.py \
- test-deterministic-ordering.sh
+ test-deterministic-ordering.sh \
+ field/test-field.sh
--- /dev/null
+#!/bin/bash
+#
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Copyright (C) 2023 Efficios, Inc.
+
+SH_TAP=1
+
+if [[ -n ${BT_TESTS_SRCDIR:-} ]]; then
+ UTILSSH=$BT_TESTS_SRCDIR/utils/utils.sh
+else
+ UTILSSH=$(dirname "$0")/../../../utils/utils.sh
+fi
+
+# shellcheck source=../../../utils/utils.sh
+source "$UTILSSH"
+
+# Directory containing the plugin
+data_dir=$BT_TESTS_DATADIR/plugins/src.ctf.fs/field
+
+test_pass() {
+ local -r mp_path=$1
+ local -r output_dir=$(mktemp -d)
+
+ run_python "$BT_TESTS_PYTHON_BIN" "$data_dir/data_from_mp.py" "$mp_path" "$output_dir"
+
+ local -r res_path=$(mktemp)
+
+ bt_cli "$res_path" /dev/null --plugin-path="$data_dir" \
+ -c sink.test-text.single "$output_dir/trace"
+ bt_diff "$res_path" "$output_dir/expect"
+ ok $? "$mp_path"
+ rm -rf "$output_dir" "$res_path"
+}
+
+plan_tests 6
+
+for mp_path in "$data_dir"/ctf-1/pass-*.mp; do
+ test_pass "$mp_path"
+done
"$BT_TESTS_SED_BIN" -i'' -e 's/\r//g' "$1"
}
+bt_remove_cr_inline() {
+ "$BT_TESTS_SED_BIN" 's/\r//g' "$1"
+}
+
# Run the Babeltrace CLI, redirecting stdout and stderr to specified files.
#
# $1: file to redirect stdout to
# Strip any \r present due to Windows (\n -> \r\n).
# "diff --string-trailing-cr" is not used since it is not present on
# Solaris.
- bt_remove_cr "$actual_file"
-
- diff -u "$expected_file" "$actual_file" 1>&2
+ diff -u <(bt_remove_cr_inline "$expected_file") <(bt_remove_cr_inline "$actual_file") 1>&2
return $?
}