It has been a while since GstValidate has been available. GstValidate has made it easier to write integration tests that check that playback and transcoding executing actions (like seeking, changing subtitle tracks, etc…) work as expected; testing at a high level rather than fine exact/fine grained data flow.
As GStreamer is applied to an ever wider variety of cases, testing often becomes cumbersome for those cases that resemble less typical playback. On one hand there is the C testing framework intended for unit tests, which is admittedly low level. Even when using something like GstHarness, checking an element outputs the correct buffers and events requires a lot of manual coding. On the other hand gst-validate so far has focused mostly on assets that can be played with a typical playbin, requiring extra effort and coding for the less straightforward cases.
This has historically left many specific test cases within that middle ground without an effective way to be tested. validateflow attempts to fill this gap by allowing gst-validate to test that custom pipelines acted in a certain way produce the expected result.
validateflow itself is a GstValidate plugin that records buffers and events flowing through a given pad and records them in a log file. The first time a test is run, this log becomes the expectation log. Further executions of the test still create a new log file, but this time it’s compared against the expectation log. Any difference is reported as an error. The user can rebaseline the tests by removing the expectation log file and running it again. This is very similar to how many web browser tests work (e.g. Web Platform Tests).
How to get it
validateflow has been landed recently on the development versions of GStreamer. Before 1.16 is released you’ll be able to use it by checking out the latest master branches of GStreamer subprojects, preferably with something like gst-build.
Make sure to update both gst-devtools. Then update gst-integration-testsuites by running the following command, that will update the repo and fetch media files. Otherwise you will get errors.
gst-validate-launcher --sync -L
Writing tests
The usual way to use validateflow is through pipelines.json
, a file parsed by the validate
test suite (the one run by default by gst-validate-launcher
) where all the necessary elements of a validateflow tests can be placed together.
For instance:
"qtdemux_change_edit_list":
{
"pipeline": "appsrc ! qtdemux ! fakesink async=false",
"config": [
"%(validateflow)s, pad=fakesink0:sink, record-buffers=false"
],
"scenarios": [
{
"name": "default",
"actions": [
"description, seek=false, handles-states=false",
"appsrc-push, target-element-name=appsrc0, file-name=\"%(medias)s/fragments/car-20120827-85.mp4/init.mp4\"",
"appsrc-push, target-element-name=appsrc0, file-name=\"%(medias)s/fragments/car-20120827-85.mp4/media1.mp4\"",
"checkpoint, text=\"A moov with a different edit list is now pushed\"",
"appsrc-push, target-element-name=appsrc0, file-name=\"%(medias)s/fragments/car-20120827-86.mp4/init.mp4\"",
"appsrc-push, target-element-name=appsrc0, file-name=\"%(medias)s/fragments/car-20120827-86.mp4/media2.mp4\"",
"stop"
]
}
]
},
These are:
pipeline
: A string with the same syntax of gst-launch describing the pipeline to use. Python string interpolation can be used to get the path to themedias
directory where audio and video assets are placed in thegst-integration-testsuites
repo by writing%(media)s
. It can also be used to get a video or audio sink that can be muted, with%(videosink)s
or%(audiosink)s
config
: A validate configuration file. Among other things that can be set, here validateflow overrides are defined, one per line, with%(validateflow)s
, which expands tovalidateflow,
plus some options defining where the logs will be written (which depends on the test name). Each override monitors one pad. The settings here define which pad, and what will be recorded.scenarios
: Usually a single scenario is provided. A series of actions performed in order on the pipeline. These are normal GstValidate scenarios, but new actions have been added, e.g. for controlling appsrc elements (so that you can push chunks of data in several steps instead of relying on a filesrc pushing a whole file and be done with it).
Running tests
The tests defined in pipelines.json
are automatically run by default when running gst-validate-launcher
, since they are part of the default test suite.
You can get the list of all the pipelines.json
tests like this:
gst-validate-launcher -L |grep launch_pipeline
You can use these test names to run specific tests. The -v
flag is useful to see the actions as they are executed. --gdb
runs the test inside the GNU debugger.
gst-validate-launcher -v validate.launch_pipeline.qtdemux_change_edit_list.default
In the command line argument above validate.
defines the name of the test suite Python file, testsuites/validate.py
. The rest, launch_pipeline.qtdemux_change_edit_list.default
is actually a regex: actually .
just happens to match a period but it would match any character (it would be more correct, albeit also more inconvenient, to use \.
instead). You can use this feature to run several related tests, for instance:
$ gst-validate-launcher -m 'validate.launch_pipeline\.appsrc_.*'
Setting up GstValidate default tests
[3 / 3] validate.launch_pipeline.appsrc_preroll_test.single_push: Passed
Statistics:
-----------
Total time spent: 0:00:00.369149 seconds
Passed: 3
Failed: 0
---------
Total: 3
Expectation files are stored in a directory named flow-expectations
, e.g.:
~/gst-validate/gst-integration-testsuites/flow-expectations/qtdemux_change_edit_list/log-fakesink0:sink-expected
The actual output log (which is compared to the expectations) is stored as a log file, e.g.:
~/gst-validate/logs/qtdemux_change_edit_list/log-fakesink0:sink-actual
Here is how a validateflow log looks.
event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE, group-id=(uint)1;
event caps: video/x-h264, stream-format=(string)avc, alignment=(string)au, level=(string)2.1, profile=(string)main, codec_data=(buffer)014d4015ffe10016674d4015d901b1fe4e1000003e90000bb800f162e48001000468eb8f20, width=(int)426, height=(int)240, pixel-aspect-ratio=(fraction)1/1;
event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=none, time=0:00:00.000000000, base=0:00:00.000000000, position=0:00:00.000000000
event tag: GstTagList-stream, taglist=(taglist)"taglist\,\ video-codec\=\(string\)\"H.264\\\ /\\\ AVC\"\;";
event tag: GstTagList-global, taglist=(taglist)"taglist\,\ datetime\=\(datetime\)2012-08-27T01:00:50Z\,\ container-format\=\(string\)\"ISO\\\ fMP4\"\;";
event tag: GstTagList-stream, taglist=(taglist)"taglist\,\ video-codec\=\(string\)\"H.264\\\ /\\\ AVC\"\;";
event caps: video/x-h264, stream-format=(string)avc, alignment=(string)au, level=(string)2.1, profile=(string)main, codec_data=(buffer)014d4015ffe10016674d4015d901b1fe4e1000003e90000bb800f162e48001000468eb8f20, width=(int)426, height=(int)240, pixel-aspect-ratio=(fraction)1/1, framerate=(fraction)24000/1001;
CHECKPOINT: A moov with a different edit list is now pushed
event caps: video/x-h264, stream-format=(string)avc, alignment=(string)au, level=(string)3, profile=(string)main, codec_data=(buffer)014d401effe10016674d401ee8805017fcb0800001f480005dc0078b168901000468ebaf20, width=(int)640, height=(int)360, pixel-aspect-ratio=(fraction)1/1;
event segment: format=TIME, start=0:00:00.041711111, offset=0:00:00.000000000, stop=none, time=0:00:00.000000000, base=0:00:00.000000000, position=0:00:00.041711111
event tag: GstTagList-stream, taglist=(taglist)"taglist\,\ video-codec\=\(string\)\"H.264\\\ /\\\ AVC\"\;";
event tag: GstTagList-stream, taglist=(taglist)"taglist\,\ video-codec\=\(string\)\"H.264\\\ /\\\ AVC\"\;";
event caps: video/x-h264, stream-format=(string)avc, alignment=(string)au, level=(string)3, profile=(string)main, codec_data=(buffer)014d401effe10016674d401ee8805017fcb0800001f480005dc0078b168901000468ebaf20, width=(int)640, height=(int)360, pixel-aspect-ratio=(fraction)1/1, framerate=(fraction)24000/1001;
Prerolling and appsrc
By default scenarios don’t start executing actions until the pipeline is playing. Also by default sinks require a preroll for that to occur (that is, a buffer must reach the sink before the state transition to playing is completed).
This poses a problem for scenarios using appsrc, as no action will be executed until a buffer reaches the sink, but a buffer can only be pushed as the result of an appsrc-push
action, creating a chicken and egg problem.
For many cases that don’t require playback we can solve this simply by disabling prerolling altogether, setting async=false
in the sinks.
For cases where prerolling is desired (like playback), handles_states=true
should be set in the scenario description. This makes the scenario actions run without having to wait for a state change. appsrc-push
will notice the pipeline is in a state where buffers can’t flow and enqueue the buffer without waiting for it so that the next action can run immediately. Then the set-state
can be used to set the state of the pipeline to playing, which will let the appsrc emit the buffer.
description, seek=false, handles-states=true
appsrc-push, target-element-name=appsrc0, file-name="raw_h264.0.mp4"
set-state, state=playing
appsrc-eos, target-element-name=appsrc0
Documentation
The documentation of validateflow, explaining its usage in more detail can be found here:
https://gstreamer.freedesktop.org/documentation/gst-devtools-1.0/plugins/validateflow.html
Looks like the documentation has been refactored and the right validateflow docs link is now https://gstreamer.freedesktop.org/documentation/gst-devtools/gst-validate-flow.html