diff --git a/.arcconfig b/.arcconfig new file mode 100644 index 0000000000..fede772326 --- /dev/null +++ b/.arcconfig @@ -0,0 +1,5 @@ +{ + "phabricator.uri" : "https://phabricator.freedesktop.org/", + "repository.callsign" : "GSTDEV", + "project": "GStreamer Validate" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..b59dbb78d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.bak +build* +mesonbuild* diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000..91690118f2 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,43 @@ +include: "https://gitlab.freedesktop.org/gstreamer/gst-ci/raw/master/gitlab/ci_template.yml" + +.local-rules: &local-rules + rules: + - changes: + - validate/launcher/ + +# Run valgrind if we changed the check.py testsuite +local valgrind core: + extends: '.valgrind fedora x86_64' + variables: + TEST_SUITE: "check.gstreamer\\..*" + <<: *local-rules + +local valgrind base: + extends: '.valgrind fedora x86_64' + variables: + TEST_SUITE: "check.gst-plugins-base\\..*" + <<: *local-rules + +local valgrind good: + extends: '.valgrind fedora x86_64' + variables: + TEST_SUITE: "check.gst-plugins-good\\..*" + <<: *local-rules + +local valgrind ugly: + extends: '.valgrind fedora x86_64' + variables: + TEST_SUITE: "check.gst-plugins-ugly\\..*" + <<: *local-rules + +local valgrind bad: + extends: '.valgrind fedora x86_64' + variables: + TEST_SUITE: "check.gst-plugins-bad\\..*" + <<: *local-rules + +local valgrind ges: + extends: '.valgrind fedora x86_64' + variables: + TEST_SUITE: "check.gst-editing-services\\..*" + <<: *local-rules diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000000..c748669396 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,20278 @@ +=== release 1.19.2 === + +2021-09-23 01:36:10 +0100 Tim-Philipp Müller + + * ChangeLog: + * NEWS: + * RELEASE: + * gst-devtools.doap: + * meson.build: + Release 1.19.2 + +2021-09-13 18:24:18 +0200 Vivienne Watermeier + + * validate/gst/validate/validate.c: + validate: fix relative paths for test files + Part-of: + +2021-09-10 17:11:29 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-override-registry.c: + validate: Fix double freeing of GstStructure + gst_validate_get_config is transfer-container only + Part-of: + +2021-08-20 09:22:28 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-utils.c: + validate-utils: Only modify structure fields that really need updates + This avoids memory corruption in users of that structure which + were (rightfullly) assuming static fields (such as name) wouldn't + change. Without this, they would be using strings which will have been freed in + the meantime. + Part-of: + +2021-07-15 14:42:51 +0200 Edward Hervey + + * validate/gst/validate/media-descriptor.c: + validate-media-descriptor: Don't check segment position field + The position field of GstSegment is meant for private usage within + elements. Don't compare the values of it when doing media-check. + Part-of: + +2021-06-08 12:28:49 -0400 Thibault Saunier + + * meson.build: + * validate/gst/validate/meson.build: + validate: use `extract_objects` to avoid rebuilding all files for the tracer + And add the tracer to the plugins list so it can be used in our + uninstalled environment. + Part-of: + +2021-06-01 15:29:21 +0100 Tim-Philipp Müller + + * meson.build: + Back to development + +=== release 1.19.1 === + +2021-06-01 00:16:57 +0100 Tim-Philipp Müller + + * ChangeLog: + * NEWS: + * RELEASE: + * gst-devtools.doap: + * meson.build: + Release 1.19.1 + +2021-05-25 21:25:14 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Error out on invalid 'foreach' iterator types + Part-of: + +2021-05-25 21:00:09 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Handle unknown type in foreach types + Part-of: + +2021-05-24 01:26:41 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + validate: Make array delemiter a line continuation char + Making its usage more friendly + Part-of: + +2021-05-24 01:20:47 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/tests/launcher_tests/foreach_array.validatetest: + validate:scenario: Allow iterating over arrays in `foreach` + We used to only support ranges, but we want to allow iterating over + values in an array too. + Part-of: + +2021-05-24 01:19:13 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-enums.h: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/gst-validate-utils.h: + * validate/gst/validate/validate.c: + * validate/tests/check/validate/utilities.c: + validate: Add a flag to allow defining how to resolve variables in structs + Part-of: + +2021-05-23 22:43:04 -0400 Thibault Saunier + + * validate/gst/validate/flow/formatting.c: + validate:flow: Log caps features + No reason not to use directy the GstCaps serialization function here + This commits avoids needing regenerated all expectations to remove + the `;` which is not generated anymore as it is simple and makes + merging simpler. + Part-of: + +2021-05-20 16:43:25 +0100 Philippe Normand + + * validate/launcher/utils.py: + validate: launcher: Simplify fakesink handling + Now the function returns either a fakeaudiosink or a fakevideosink, depending on + the media type. + Part-of: + +2021-05-20 10:45:34 -0400 Thibault Saunier + + * validate/gst/validate/flow/formatting.c: + * validate/gst/validate/flow/formatting.h: + * validate/tests/launcher_tests/simple_repeat/flow-expectations/log-sink-sink-expected: + validate:flow: Sort fields in serialized structures + Otherwise change in element implementations could lead to meaningless + breakages + +2021-01-12 15:38:03 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Allow forcing running action on idle from scenario file + Part-of: + +2021-01-12 15:37:08 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/tests/launcher_tests/run_command_with_envvars.validatetest: + validate:scenario: Add a `run-command` action type + Part-of: + +2021-01-12 15:36:05 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-monitor.c: + validate:monitor: Only get_name on GstObject + GObject don't have such method! + Part-of: + +2021-04-29 10:09:05 +0200 Stéphane Cerveau + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + validate: add config file support + Each test can now use a config file for + the given media file used to test. + Part-of: + +2021-04-21 13:54:38 +0200 François Laignel + + * validate/tests/check/validate/padmonitor.c: + * validate/tests/check/validate/reporting.c: + Use gst_element_request_pad_simple... + Instead of the deprecated gst_element_get_request_pad. + Part-of: + +2021-04-19 15:00:18 +0200 Edward Hervey + + * validate/data/scenarios/seek_forward.scenario: + scenario: Fix action variable name + This was always meant to be `on-message=eos` (like in fast_forward.scenario) + Fixes #58 + Part-of: + +2021-03-19 17:16:33 +1100 Matthew Waters + + * validate/tests/check/validate/test-utils.c: + gst: don't use volatile to mean atomic + volatile is not sufficient to provide atomic guarantees and real atomics + should be used instead. GCC 11 has started warning about using volatile + with atomic operations. + https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1719 + Discovered in https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/issues/868 + Part-of: + +2021-02-26 15:32:29 +0100 Stéphane Cerveau + + * validate/data/bash-completion/completions/gst-validate-1.0: + bash-completion: add gst-validate script + Part-of: + +2021-01-30 10:01:54 -0600 Brady J. Garvin + + * validate/launcher/apps/gstcheck.py: + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + validate:launcher: Ensure a positive job count. + The default number of jobs to use is half of the available cores + rounded down, but in situations where only one core is available (such + as under some VMs), this means that `gst-validate-launcher` defaults + to using zero jobs, a case that the test-running code is not prepared + to handle. + This change makes the code match the documentation for the `--jobs` option, + guards against negative values both in the default setting and in argument + parsing, and introduces some defensive programming to prevent other situations + where the code might try to use zero jobs. + Part-of: + +2021-01-08 08:38:12 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: Plug a leak + +2021-01-05 10:06:40 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Do not check strv length on NULL pointers + This is not legal + Part-of: + +2020-12-15 18:18:29 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/validate.c: + * validate/tests/launcher_tests/foreach.validatetest: + * validate/tests/launcher_tests/foreach_deep.validatetest: + validate: Allow using the new nested structure syntax + And port the deeply nested tests we have + Part-of: + +2020-12-15 18:18:29 -0300 Thibault Saunier + + * validate/gst/validate/validate.c: + validate: Only consider the first pipeline when using test files + And port the deeply nested tests we have + Part-of: + +2020-12-15 18:18:29 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Add missing GstValidateAction annotations + Part-of: + +2020-12-15 18:15:50 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Add an `expected-values` parameter to `wait, message-type=XX` + Allowing more precise filtering of the message we are waiting for. + Part-of: + +2020-12-15 18:02:00 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: scenario: Add a GstValidateScenario::action-done signal + Allowing application to know when a specific action is done. + Part-of: + +2020-12-15 18:00:58 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-scenario.c: + validate: Enhance printing action execution information + Part-of: + +2020-12-15 17:58:51 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-bin-monitor.h: + validate: Add an API to get the bin monitor scenario + This is useful for applications that use Validate directly. + Part-of: + +2020-12-10 16:26:15 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Ensure that messages are handled from the right thread + Part-of: + +2020-11-29 10:05:36 +0000 Philippe Normand + + * debug-viewer/org.freedesktop.GstDebugViewer.appdata.xml.in: + debug-viewer: Make appdata valid again + Part-of: + +2020-12-08 11:46:37 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Fix the refcount management for actions in structures + Handling the refcounting the same whether the action is blocking or not + as we were leaking a ref for non-blocking waits. + Part-of: + +2020-12-08 10:57:15 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Minor cleanup + Part-of: + +2020-12-08 10:55:28 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Use gst_validate_action_*ref everywhere. + Part-of: + +2020-11-30 23:06:18 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Add support to check properties of object properties + And recursively + Part-of: + +2020-11-06 12:43:57 +0100 Stéphane Cerveau + + * validate/launcher/main.py: + validate: add sync-version + Be able by the command line to change the sync version + which is usually the GST_VALIDATE_TESTSUITE_VERSION + from the test suite + Part-of: + +2020-11-19 22:41:40 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Add a 'non-blocking' flag to the `wait` signal + This way we can execute actions that will lead to the signal + emission later in the execution. + Part-of: + +2020-11-20 10:16:28 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario: Rename 'interlaced' action to 'non-blocking' + It is a better and more understandable naming. + Part-of: + +2020-10-23 22:40:41 +0900 Seungha Yang + + * validate/gst-libs/gst/video/meson.build: + * validate/plugins/ssim/meson.build: + * validate/tools/meson.build: + meson: Check cairo-png dependency + Should check whether libpng dependent methods are available or not + Fixes: https://gitlab.freedesktop.org/gstreamer/gst-build/-/issues/128 + Part-of: + +2020-11-04 18:49:03 +0530 Nirbheek Chauhan + + * meson.build: + meson: Enable some MSVC warnings for parity with GCC/Clang + This makes it easier to do development with MSVC by making it warn + on common issues that GCC/Clang error out for in our CI configuration. + Continuation from https://gitlab.freedesktop.org/gstreamer/gst-build/-/merge_requests/223 + Part-of: + +2020-10-16 12:49:02 +0200 Stéphane Cerveau + + * meson.build: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + meson: update glib minimum version to 2.56 + In order to support the symbol g_enum_to_string in various + project using GStreamer ( gst-validate etc.), the glib minimum + version should be 2.56.0. + Remove compat code as glib requirement + is now > 2.56 + Version used by Ubuntu 18.04 LTS + Part-of: + +2020-03-22 09:51:40 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: Bump hard timeouts for all transcodin tests + Part-of: + +2020-03-21 11:57:51 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + * validate/launcher/utils.py: + launcher: Avoid variable framerate when encoding to theora + It is not supported by theoraenc. + Part-of: + +2020-03-20 09:05:52 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate: Scale down even more to speed up encoding in VP9 + From 3min to 50secs to execute here. + Part-of: + +2020-03-19 18:48:08 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/meson.build: + validate:transcoding: Port to GstTranscoder + Remove flag to force EOS on sigintr, making it the only choice + Also add support for variable framerate + Part-of: + +2020-03-19 18:41:24 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.h: + * validate/tests/check/validate/padmonitor.c: + validate: Use pad.last_flowret instead of trying to compute it ourselves + Which makes it more accurate + Part-of: + +2020-03-19 18:26:58 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-reporter.c: + report: Add a way to force backtraces on reports + And stop report simple debug message + Part-of: + +2020-03-17 12:19:46 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: Add a way to define test timeout from dicts + Part-of: + +2020-08-06 08:00:53 -0400 Xavier Claessens + + * meson.build: + * validate/gst/validate/meson.build: + * validate/meson.build: + * validate/pkgconfig/gst-validate-uninstalled.pc.in: + * validate/pkgconfig/gst-validate.pc.in: + * validate/pkgconfig/meson.build: + Meson: Use pkg-config generator + +2020-09-10 21:38:00 +0000 Jordan Petridis + + * validate/data/gstvalidate.supp: + gstvalidate.supp: update location of gst.supp + Part-of: + +2020-05-12 09:26:40 -0400 Thibault Saunier + + * docs/gst-validate-launcher.md: + docs: Update gst-validate-launcher documentation + Part-of: + +2020-09-08 17:30:56 +0100 Tim-Philipp Müller + + * .gitlab-ci.yml: + ci: include template from gst-ci master branch again + +2020-09-08 16:59:12 +0100 Tim-Philipp Müller + + * meson.build: + Back to development + +=== release 1.18.0 === + +2020-09-08 00:10:18 +0100 Tim-Philipp Müller + + * .gitlab-ci.yml: + * ChangeLog: + * NEWS: + * RELEASE: + * gst-devtools.doap: + * meson.build: + Release 1.18.0 + +2020-08-26 15:47:23 +0100 Tim-Philipp Müller + + * validate/launcher/testsuites/check.py: + Revert "Revert "launcher: add webrtcbin datachannel tests to valgrind skip list"" + This reverts commit ff79af843bc40a554795ee2a0682cfd12272dd24. + Looks like most of these are still problematic and flaky in valgrind, + so re-add to skiplist for now. + https://gitlab.freedesktop.org/thaytan/gst-plugins-base/-/jobs/4275045 + +2020-08-24 23:59:14 +0100 Tim-Philipp Müller + + * validate/launcher/testsuites/check.py: + Revert "launcher: add webrtcbin datachannel tests to valgrind skip list" + This reverts commit 369c74941f1607b421bc2f16edcaea0b887926a9. + Fixes https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/issues/1383 + Part-of: + +=== release 1.17.90 === + +2020-08-20 16:16:50 +0100 Tim-Philipp Müller + + * ChangeLog: + * NEWS: + * RELEASE: + * gst-devtools.doap: + * meson.build: + Release 1.17.90 + +2020-08-13 21:55:09 -0400 Thibault Saunier + + * validate/launcher/testsuites/check.py: + validate:check: Blacklist new ges valgrind test + I can't find what the issue is and the test is very long anyway + Part-of: + +2020-08-13 20:51:52 -0400 Thibault Saunier + + * validate/gst/validate/flow/formatting.c: + * validate/gst/validate/flow/gstvalidateflow.c: + * validate/meson.build: + validate: flow: Plug some leaks + Part-of: + +2020-08-13 16:19:50 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Plug some leaks + Part-of: + +2020-08-14 00:27:25 +0100 Tim-Philipp Müller + + * validate/launcher/testsuites/check.py: + launcher: add webrtcbin datachannel tests to valgrind skip list + They were previously not run because the sctp plugin wasn't built + but they will be run now that we bundle libusrsctp. + https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/1465 + Part-of: + +2020-07-25 21:12:00 +0100 Tim-Philipp Müller + + * validate/gst/validate/gst-validate-bin-monitor.h: + * validate/gst/validate/gst-validate-element-monitor.h: + * validate/gst/validate/gst-validate-monitor.h: + * validate/gst/validate/gst-validate-override.h: + * validate/gst/validate/gst-validate-pad-monitor.h: + * validate/gst/validate/gst-validate-pipeline-monitor.h: + * validate/gst/validate/gst-validate-reporter.h: + * validate/gst/validate/gst-validate-runner.h: + * validate/gst/validate/gst-validate-scenario.h: + * validate/gst/validate/media-descriptor-parser.h: + * validate/gst/validate/media-descriptor-writer.h: + * validate/gst/validate/media-descriptor.h: + validate: silence g-ir-scanner warnings about GST_IS_VALIDATE_* + Which it complains about because we say our prefix is 'gst_validate' + so it should really be GST_VALIDATE_IS_* instead. + Hide the boilerplate defines from g-ir-scanner, it doesn't need + to process them. + Fixes #46 + Part-of: + +2020-07-27 09:06:39 -0400 Thibault Saunier + + * validate/gst/validate/meson.build: + Revert "validate: fix up gir namespace and symbol prefix" + This reverts commit b73e81614021a856fc7d3ff221a9bbf5ebf2ce6e. + Part-of: + +2020-07-25 23:40:05 +0100 Tim-Philipp Müller + + * validate/gst/validate/meson.build: + validate: fix up gir namespace and symbol prefix + Change gir namespace and symbol prefix from + GstValidate / gst_validate to Gst / gst, same + as we do for other libs like GstVideo etc. + Helps with warnings about GST_IS_VALIDATE_* + Fixes #46, Closes !214 + Part-of: + +2020-07-25 13:57:01 -0400 Thibault Saunier + + * validate/gst/validate/media-descriptor-writer.c: + validate: Fix media descriptor mp3 like formats + Part-of: + +2020-07-25 20:27:48 +0100 Tim-Philipp Müller + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: reflow #ifdef to work around bogus g-ir-scanner warning + Looks like a scanner bug. + The endif comments are the wrong way round too, but that's not it. + gst-validate-scenario.c:126: mismatched #endif /* G_HAVE_GNUC_VARARGS */ + Part-of: + +2020-07-03 02:04:19 +0100 Tim-Philipp Müller + + * meson.build: + Back to development + +=== release 1.17.2 === + +2020-07-03 00:37:27 +0100 Tim-Philipp Müller + + * ChangeLog: + * NEWS: + * RELEASE: + * gst-devtools.doap: + * meson.build: + Release 1.17.2 + +2020-06-18 12:46:39 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Always check if scenario is done from the right thread + Action will be set_done from the right thread and we will check if the action is done from there + Part-of: + +2020-06-16 15:34:04 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-reporter.c: + validate: Print errors on action failures + Part-of: + +2020-06-15 17:32:13 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + validate: Plug some leaks + Part-of: + +2020-06-15 16:17:55 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/tests/launcher_tests/simple_interlaced_action.validatetest: + validate: Move action finalization to _set_done where it belongs + gst_validate_action_set_done is the place where we should finalize the + action, not in `execute_next`, this way we better handle printing + interlaced action finalization too. + Part-of: + +2020-06-15 10:50:14 -0400 Thibault Saunier + + * validate/data/scenarios/change_state_intensive.scenario: + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/tests/launcher_tests/foreach.validatetest: + * validate/tests/launcher_tests/foreach/flow-expectations/log-sink-sink-expected: + * validate/tests/launcher_tests/foreach_deep.validatetest: + * validate/tests/launcher_tests/foreach_repeat.validatetest: + validate:scenario: Replace the `sub-action` with a `foreach` action type + Sub-actions were really hard to use and conceptually weird. The + implementation was ugly and made the code complex for nothing. + Instead this commit introduces a `foreach` action type which allows + repeating actions passed in an `actions` array the number of time + specified by any `GstIntRange` value defined in the structure or its + `repeat` field. + This commit also makes sure that all action got through + gst_validate_action_set_done upon finalization. + + Cleanup surrounding code + + Add tests + Part-of: + +2020-06-15 09:17:55 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/tests/launcher_tests/simple_repeat.validatetest: + * validate/tests/launcher_tests/simple_repeat/flow-expectations/log-sink-sink-expected: + validate: scenario: Implement 'repeat' by copying actions + Instead of trying to reuse the same action structure and deal with + that in a complex way, copy the action the required number of times. + And add a simple test + Part-of: + +2020-06-15 09:32:23 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Round results of expressions in a sensible way + Part-of: + +2020-06-15 10:54:20 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + launcher: Keep running tests forever on KNOWN_ERROR + Part-of: + +2020-06-15 09:37:21 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate: Add private action type to check number of action type calls + Part-of: + +2020-06-15 09:14:16 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Avoid dereferencing NULL structure + Part-of: + +2020-06-15 09:08:51 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-runner.c: + validate: Fix marking expected issues as criticals + And never mark a repeat expected reports as repeated + Part-of: + +2020-06-12 10:08:25 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + validate: Add a way to use the expression parser in any field + Part-of: + +2020-06-12 10:05:57 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + validate: Allow variables to be set with other types than strings + And use value serialization from GStreamer to convert + Part-of: + +2020-06-12 09:58:24 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Add an action to remove a feature/plugin from the registry + Part-of: + +2020-06-10 17:18:49 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/tests/launcher_tests/check_set_props_and_time_props.validatetest: + valiadate: Add a test for setting/checking (timed) properties + Part-of: + +2020-06-10 16:44:04 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Add action to set and check several properties at once + Part-of: + +2020-06-10 15:39:12 -0400 Thibault Saunier + + * meson.build: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/meson.build: + * validate/tools/meson.build: + validate: Add an action type to set timed value properties + Part-of: + +2020-06-19 10:26:17 +0100 Philippe Normand + + * debug-viewer/org.freedesktop.GstDebugViewer.appdata.xml.in: + * debug-viewer/screenshots/gst-debug-viewer.png: + debug-viewer: Add screenshot + Part-of: + +2020-06-20 00:28:39 +0100 Tim-Philipp Müller + + * meson.build: + Back to development + +=== release 1.17.1 === + +2020-06-19 19:27:58 +0100 Tim-Philipp Müller + + * ChangeLog: + * NEWS: + * RELEASE: + * gst-devtools.doap: + * meson.build: + Release 1.17.1 + +2020-06-12 19:31:41 +0530 Vivek R <123vivekr@gmail.com> + + * validate/gst/validate/flow/formatting.c: + validate: flow: record GstRegionOfInterestMeta + Part-of: + +2020-06-15 18:37:51 -0400 Thibault Saunier + + * validate/launcher/main.py: + validate:launcher: Ensure that the main directory exists + Fixes https://gitlab.freedesktop.org/gstreamer/gst-devtools/-/issues/53 part 1 + Part-of: + +2020-06-15 18:06:33 -0400 Thibault Saunier + + * validate/tests/check/meson.build: + * validate/tests/launcher_tests/meson.build: + * validate/tests/meson.build: + validate:tests: Cleanup the way set environment vars + Part-of: + +2020-06-15 17:56:54 -0400 Thibault Saunier + + * validate/tools/gst-validate-launcher.in: + validate: Stop trying to support uninstalled autotools + Fix https://gitlab.freedesktop.org/gstreamer/gst-devtools/-/issues/53 part 2 + Part-of: + +2020-06-09 17:29:08 -0400 Thibault Saunier + + * validate/gst/validate/flow/formatting.c: + validateflow: Plug leak + Part-of: + +2020-06-08 14:01:49 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Fix waiting for signal + The signal callback signature was just wrong and not generic leading to + crash if waiting for any signal that didn't match it. This commit fixes + it. + Part-of: + +2020-06-08 14:00:44 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Add an option to set properties on all instances + Part-of: + +2020-06-08 13:45:26 -0400 Thibault Saunier + + * validate/gst/validate/flow/formatting.c: + * validate/gst/validate/flow/formatting.h: + * validate/gst/validate/flow/gstvalidateflow.c: + validate:flow: Add a way to dump buffer content as hex + Useful in unit tests with very small buffers + Part-of: + +2020-06-02 19:10:14 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Cleanup implementation of appsrc-push + Part-of: + +2020-05-07 09:16:11 -0400 Thibault Saunier + + * validate/tools/gst-validate.c: + validate: Remove useless condition + CID 1462652 + Part-of: + +2020-05-07 09:09:14 -0400 Thibault Saunier + + * validate/plugins/ssim/gstvalidatessim.c: + validate:ssim: Avoid dereferencing NULL pointer + CID 1462650 + Part-of: + +2020-05-07 09:06:32 -0400 Thibault Saunier + + * validate/tools/gst-validate.c: + validate: Give a proper argv[0] when running test files + Fixes CID 1462613 + Part-of: + +2020-05-07 09:05:06 -0400 Thibault Saunier + + * validate/tools/gst-validate-transcoding.c: + validate:transcoding: Exit after printing the help + Fixes CID 1455575 + Part-of: + +2020-05-07 08:49:34 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Ensure Scenario isn't NULL when preparing action + Part-of: + +2020-06-03 14:29:22 -0400 Thibault Saunier + + * docs/fakesrc.simple.validatetest: + * docs/fakesrc.simple.validatetest.ini: + * docs/fakesrc.simple/flow-expectations/log-sink-sink-expected: + * docs/gst-validate-config.md: + * docs/gst-validate-flow.md: + * docs/gst-validate-test-file.md: + * docs/plugins/validateflow.md: + * docs/sitemap.txt: + validate: Update documentation now core plugins are integrated + Part-of: + +2020-06-03 10:55:22 -0400 Thibault Saunier + + * validate/gst/validate/flow/formatting.c: + * validate/gst/validate/flow/formatting.h: + * validate/gst/validate/flow/gstvalidateflow.c: + * validate/gst/validate/flow/gstvalidateflow.h: + * validate/gst/validate/flow/meson.build: + * validate/gst/validate/gst-validate-extra-checks.c: + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/meson.build: + * validate/gst/validate/validate.c: + * validate/plugins/extra_checks/meson.build: + * validate/plugins/meson.build: + validate: Make extracheck and flow part of core instead plugins + It makes things more complex and doesn't bring anything! + Part-of: + +2020-06-03 09:32:32 +0200 Edward Hervey + + * validate/launcher/baseclasses.py: + launcher: Limit copies of massive debug logs in markdown file + When debugging is activated, we could end up with log files ranging in the + multi-megabyte or even gigabyte range. Copying those is expensive from a cpu/io + point of view in addition to clobbering the storage. + Instead of always copying those files, check if they are smaller than 500kB. If + not, don't copy them and instead provide a link to their location. + Fixes #52 + Part-of: + +2020-05-30 15:54:31 -0400 Thibault Saunier + + * docs/plugins/validateflow.md: + * validate/plugins/flow/formatting.c: + * validate/plugins/flow/formatting.h: + * validate/plugins/flow/gstvalidateflow.c: + validateflow: Allow specifying checksum type + And add an extra mode 'checksum-as-id' which basically numerate + buffers checksums as they are being received so that it is simpler + to compare expectations when you are tracking buffers from both + sinkpads and srcpads. + Part-of: + +2020-05-29 18:11:54 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/validate.c: + validate: Add a parameter to force waiting on the test clock + This allows to wait for a new buffer to reach the sink without + actually cranking that buffer, allowing to quite the test without + waiting for EOS in a 100% reproducible way + Part-of: + +2020-05-29 18:11:11 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/validate.c: + validate: Error out when a config hasn't been used at all + It probably means a plugin is not available + Part-of: + +2020-05-28 00:16:57 +0100 Tim-Philipp Müller + + * ChangeLog: + * NEWS: + * RELEASE: + * gst-devtools.doap: + * validate/AUTHORS: + * validate/meson.build: + Ship validate as part of a gst-devtools tarball + Part-of: + +2020-05-27 20:59:41 +0100 Tim-Philipp Müller + + * meson.build: + * meson_options.txt: + meson: make debug_viewer a feature option + ... and disable by default. + Part-of: + +2020-05-28 17:04:20 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + validate: Fix mixup in quarks usages + Part-of: + +2020-05-27 19:35:26 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + validate: Add missing return func when GLib < 2.50 + CID 1463854 + Part-of: + +2020-05-26 23:25:48 -0400 Thibault Saunier + + * docs/gst-validate-action-types.md: + * docs/gst-validate-test-file.md: + * docs/plugins/validateflow.md: + * validate/gst/validate/gst-validate-report.c: + validate: Update documentation + Part-of: + +2020-05-05 18:09:08 -0400 Thibault Saunier + + * docs/plugins/fakesrc.simple.validatetest: + * docs/plugins/fakesrc.simple.validatetest.yaml: + * docs/plugins/fakesrc.simple/flow-expectations/log-sink-sink-expected: + * docs/plugins/validateflow.md: + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/validate.c: + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + * validate/plugins/flow/gstvalidateflow.c: + validateflow: Add a way to configure when to generate expectations + By default, generate them whenever the file is missing but adding a way + to override that with `validateflow,generate-expectations=true` to force + regenerating them or setting `validateflow,generate-expectations=false` + to disallow generating them (on CI servers for example) + Also update the validateflow documentation to take that into account + and remove references to pipeline.json file which is now gone! + Part-of: + +2020-05-14 19:22:18 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Minor cleanup around pipeline change state management + Part-of: + +2020-05-14 18:45:11 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Mark seek as done only when reaching next state + There is a race where following actions could generate a + flush-start/flush-stop dance but the state change resulting from the + seek hasn't been committed yet, leading to the ASYNC_START being + ignored by GstBin since its pending_state is not VOID when receiving + the ASYNC_START message. + Conceptually it is totally correct to consider an action done when + the state change of the pipeline is stabilized.. + Part-of: + +2020-05-26 15:55:55 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Stop wrongly erroring on testsuite loading failure + When the testsuite was actually already loaded as the .py + file was explicitly passed in + Part-of: + +2020-05-26 15:53:47 -0400 Thibault Saunier + + * validate/gst/validate/validate.c: + validate: Fix loading configs from a caps + Part-of: + +2020-05-23 00:38:32 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/gst-validate-utils.h: + * validate/plugins/flow/gstvalidateflow.c: + flow: Use bat to color diffs when possible + Adding a function to check if can output colored logs + Part-of: + +2020-05-15 11:27:12 -0400 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + launcher: check: Properly set PLUGIN_PATH and registry when running in gst-build + This makes registry + Part-of: + +2020-05-13 18:25:00 -0400 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + * validate/launcher/baseclasses.py: + launcher: Ensure that -j tests run in parallel when running forever + So that you can reproduce the issue you want faster! + Part-of: + +2020-05-12 09:26:40 -0400 Thibault Saunier + + * docs/gst-validate-launcher.md: + docs: Update gst-validate-launcher documentation + Part-of: + +2020-03-10 11:52:35 -0300 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + * validate/launcher/utils.py: + launcher: Add support for running tests inside rr + Allowing us to easily run the tests forever and then replay the + failures! + https://rr-project.org/ + Part-of: + +2020-05-14 12:35:40 +0200 Guillaume Desmottes + + * validate/gst/validate/gst-validate-utils.c: + validate: add missing gir annotation + Part-of: + +2020-05-22 18:00:04 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: Initialize variable correctly + ret needs to be always reset to FALSE *before* checking attempting to load the + individual files. Otherwise there's the possibility it would silently accept an + invalid scenario name + Part-of: + +2020-05-22 17:56:40 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: Remove unused variable + We only need to check whether the field is present and of a given type + Part-of: + +2020-05-15 11:26:10 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + validate: Stop using g_file_peek_path + It was introduced in 2.56 so is too recent + Fixes https://gitlab.freedesktop.org/gstreamer/gst-devtools/-/issues/51 + Part-of: + +2020-05-05 13:52:52 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/gst-validate-utils.h: + * validate/gst/validate/validate.c: + * validate/tests/check/validate/utilities.c: + validate: Add including support in the structure file parser + Adding proper error reporting support + Part-of: + +2020-05-08 17:35:59 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + validate: utils: Do not try to replace vars in debug info fields + Part-of: + +2020-05-08 17:35:39 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + validate: Fix parsing validate tests files with vars on windows + And ensure that we escape windows path in variables + Part-of: + +2020-05-03 01:24:32 -0400 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + validate:launcher:check: Add support for gst-tester-1.0 + Part-of: + +2020-05-07 00:23:07 -0400 Thibault Saunier + + * docs/gst-validate-test-file.md: + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/validate.c: + * validate/launcher/baseclasses.py: + * validate/tests/launcher_tests/check_set_prop_never_called_error.validatetest: + * validate/tests/launcher_tests/not_negotiated.accept_caps_failure.validatetest: + * validate/tests/launcher_tests/test_validate.py: + * validate/tools/gst-validate.c: + validate: Add support for known-issues in the .validatetest + And add some tests about remaining actions failures + Part-of: + +2020-05-06 22:36:59 -0400 Thibault Saunier + + * validate/gst/validate/validate.c: + validate: Ensure a meta structure is found in test files + Part-of: + +2020-05-06 22:20:58 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Check remaining action on stop, not EOS + When ignoring EOS, on addition action could end up not being executed + and no error was reported which was wrong. + Part-of: + +2020-05-04 17:59:28 -0400 Thibault Saunier + + * validate/launcher/main.py: + validate: Fix rendering destination directory path creation + We were ending up creating file:/some/path in cwd + Part-of: + +2020-05-04 16:59:54 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/validate.c: + * validate/launcher/baseclasses.py: + * validate/tools/gst-validate.c: + validate: Plug some leaks + Part-of: + +2020-05-03 01:22:04 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-runner.c: + validate: Add details on all g_log message reports. + Part-of: + +2020-05-03 01:20:19 -0400 Thibault Saunier + + * docs/gst-validate-config.md: + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/gst-validate-utils.h: + * validate/launcher/baseclasses.py: + * validate/launcher/reporters.py: + * validate/tools/gst-validate-rtsp-server.c: + * validate/tools/gst-validate.c: + validate: Add a mechanism to mark tests as skipped + And use it when a plugin is missing and the user didn't ask for + failure when it happens + And use the TAP[0] synthax to report it + [0]: https://testanything.org + Part-of: + +2020-05-03 00:54:56 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-media-info.c: + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/media-descriptor-writer.c: + * validate/gst/validate/validate.c: + * validate/plugins/extra_checks/gstvalidateextrachecks.c: + * validate/plugins/flow/gstvalidateflow.c: + * validate/tools/gst-validate-images-check.c: + * validate/tools/gst-validate-media-check.c: + * validate/tools/gst-validate-rtsp-server.c: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: Enhance issue reporting from structures + And properly bail out when required + This is preparatory work for gst-test in core which will use the TAP + protocol + Part-of: + +2020-05-05 22:57:08 -0400 Thibault Saunier + + * validate/plugins/flow/formatting.c: + validate:flow: Handle some more segment fields filtering + Those slept through when implementing filtering + Part-of: + +2020-04-09 16:04:53 -0400 Thibault Saunier + + * validate/plugins/ssim/gstvalidatessim.c: + ssim: Minor improvements on the way we attach to pads + Part-of: + +2018-06-05 17:56:36 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: Refactor seek handling + * Store all seek values into a list of pending seeks instead + of hardcoding some values + * Store all segments that sinks received + * Match segments to seeks when all sinks received segments with + the same seqnum + * Detect when a seek did *not* result in segments with identical + matching seqnums + Should allow checking for all types of seek handling, including + flush-less seeks + Part-of: + +2018-06-15 10:52:46 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + validate-report: Add new check for top-level seek + This issue is to detect seeks that don't result in segments + with identical seqnums. This check can be done at the top-level + scenario + Part-of: + +2018-06-05 17:55:29 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + validate-pad-monitor: Post GstBaseSink SEGMENT on the bus + Allows higher-level bin or app (like validate-scenario) to know + what each sink currently has in terms of SEGMENT. + Part-of: + +2018-06-05 17:53:51 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-element-monitor.c: + * validate/gst/validate/gst-validate-element-monitor.h: + validate-element-monitor: Detect basesink elements + And add a macro + Part-of: + +2018-06-05 17:51:44 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: Add logging for scenario lock taking/releasing + Part-of: + +2020-04-30 12:39:44 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.h: + pad-monitor: Reliably track pending seeks + Instead of overriding all values when receiving a seek, store + them as a list of expected values. + This allows handling several seeks in a row, like non-flushing + seeks. + Part-of: + +2020-04-28 23:06:24 -0400 Thibault Saunier + + * docs/gst-validate-config.md: + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/validate.c: + validate: Allow overidding issue severity from configs + Refactoring sensibly to allow getting configs outside the `core` namespace + and outside plugin names. + The `GST_VALIDATE_OVERRIDE` env variable should probably be removed + all together at some point. + Part-of: + +2020-04-28 23:26:13 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/tools/gst-validate.c: + validate: Add an option to describe issue types + Part-of: + +2020-04-28 12:51:21 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + * validate/plugins/flow/gstvalidateflow.c: + * validate/tools/gst-validate.c: + validate: Show the exact file line when error out in structure files + And minor stdout enhancements + Part-of: + +2020-04-28 21:30:29 -0400 Thibault Saunier + + * validate/launcher/testsuites/check.py: + validate:launcher: Mark some more tests as too long for valgrind + +2020-04-27 21:46:02 +0000 Bilal Elmoussaoui + + * debug-viewer/org.freedesktop.GstDebugViewer.appdata.xml.in: + Apply suggestion to debug-viewer/org.freedesktop.GstDebugViewer.appdata.xml.in + Part-of: + +2020-01-23 15:53:03 +0000 Bilal Elmoussaoui + + * debug-viewer/org.freedesktop.GstDebugViewer.appdata.xml.in: + Metainfo: backport flathub fixes + Part-of: + +2020-03-19 18:25:28 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + validate: Do not check pulling thread when thread is paused + With decodebin3 we have cases where a task has been started in + the `typefind` element but the demuxer is the one pulling (from + its own thread) + Part-of: + +2020-04-24 23:33:16 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Add a check-position action type + Part-of: + +2020-04-24 23:32:59 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario: Enhance failures messages + Part-of: + +2020-04-13 16:23:32 -0400 Thibault Saunier + + * docs/gst-validate-test-file.md: + * meson.build: + * meson_options.txt: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/meson.build: + validate: Add a way to run a TestClock in scenarios + A TestClock will be used automatically when a scenario has a + `crank-clock` action. + And make `validate` and `debug-viewer` options features in meson, + no reason they weren't and now we require gst-check to build validate + Part-of: + +2020-04-24 15:41:10 -0400 Thibault Saunier + + * docs/gst-validate-scenarios.md: + * docs/gst-validate-test-file.md: + * docs/sitemap.txt: + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/gst-validate-utils.h: + * validate/gst/validate/validate.c: + * validate/gst/validate/validate.h: + * validate/launcher/apps/gstvalidate.py: + * validate/tools/gst-validate.c: + validate: Introduce the concept of "Test files" + This way we can have a single file that wraps scenarios, + `gst-validate-1.0` arguments, as well as a configuration. + It changes the name of `description` of scenarios to use `meta` + The goal is to replace tests describes in python with dictionary + to fully self contained `.validatetest` files which look like: + ``` + meta, + handles-states=true, + ignore-eos=true, + gst-validate-args = { + "videotestsrc pattern=blue ! video/x-raw,format=I420,framerate=1/1 ! timeoverlay ! $(videosink) name=videosink allocation-meta-flags=0", + }, + configs = { + "$(validateflow), pad=videosink:sink, buffers-checksum=true, ignored-fields={\"buffers=meta\", }", + } + play + seek, start=0.0, stop=5.0, flags=accurate+flush, rate=1.0 + crank-clock, expected-elapsed-time=0.0 + crank-clock, repeat=4, expected-elapsed-time=1.0 + crank-clock, expected-elapsed-time=1.0 + stop, on-message=eos + ``` + +2020-04-23 20:11:14 -0400 Thibault Saunier + + * validate/plugins/flow/gstvalidateflow.c: + validate:flow: Sensibly improve stdout + +2020-04-23 20:10:48 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + validate: Fix criticals around NULL structure usage + +2020-04-23 20:09:53 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Fix 'repeat' parameter on non ASYNC action types + +2020-04-22 21:13:06 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Do not consider action with 'on-message' as on addition + +2020-04-09 16:01:25 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-reporter.c: + validate: report: Handle when reporting NULL action + And add information about the action repeat state. + +2020-04-22 13:02:29 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + validate: Handle comments in multiline expressions + Part-of: + +2020-04-22 11:27:16 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/gst-validate-utils.h: + * validate/tests/check/meson.build: + * validate/tests/check/validate/utilities.c: + validate: Fix multi variable in a single structure field + We were keeping using the GMatchInfo even after modifying the string + which is explicitly stated as invalid in the GRegex documentation + Part-of: + +2020-04-21 15:48:20 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Fix 'on-message' actions execution + Part-of: + +2020-04-21 15:28:00 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + validate: Add { and [ as line continuation markers + Part-of: + +2020-04-13 15:38:05 -0400 Nicolas Dufresne + + * validate/launcher/main.py: + validate-launcher: Fix syntax error + This error prevents downloading assets from scratch. This regression was + introduced by MR !145 / commit 2581fef6843bfb53f3fc6f629577c1f013ef84e7 + +2020-04-07 18:33:08 -0400 Nicolas Dufresne + + * validate/gst/validate/gst-validate-utils.c: + utils: Fix double free in error case + This was detected by Coverity. The content point would have been freed gain in + the done: label. + CID 1461289 + +2020-03-23 21:28:45 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-reporter.c: + validate: Use gst_print for validate report messages + +2020-03-17 11:51:32 -0400 Nicolas Dufresne + + * .gitlab-ci.yml: + ci: Port from only: to rules + This fixed CI breakage introduced by gst-ci!247 + +2020-03-10 11:50:26 -0300 Thibault Saunier + + * validate/data/gstvalidate.supp: + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/plugins/flow/gstvalidateflow.c: + validate: Plug some leaks + And add some valgrind suppression for fontconfig + +2020-03-04 11:07:32 -0300 Thibault Saunier + + * docs/plugins/ssim.md: + * validate/gst-libs/gst/video/gstvalidatessim.c: + * validate/gst-libs/gst/video/gstvalidatessim.h: + * validate/plugins/ssim/gstvalidatessim.c: + * validate/tools/gst-validate-images-check.c: + validate:ssim: Allow specifying file framerate to use frame numbers during comparison + +2020-03-03 21:36:21 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario: Make the action->prepare function return a GstValidateExecuteActionReturn + Implementers might want to report the error themselves + +2020-02-28 13:51:58 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Add a way to check last frame number + This introduces a new 'timecode-frame-number' in the 'check-last-sample' + action type se we can verify the number of output frames. + +2020-02-20 08:52:38 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + validate: Enhance debug message on invalid expression function call + +2020-02-04 18:13:51 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + validate:utils: Allow plain string in `gst_validate_utils_get_strv` + +2020-02-10 14:55:18 -0300 Thibault Saunier + + * validate/gst-libs/gst/video/gstvalidatessim.c: + * validate/gst-libs/gst/video/gstvalidatessim.h: + * validate/plugins/ssim/gstvalidatessim.c: + validatessim: Avoid having ':' in file names + This is unsupported on windows + +2020-02-03 16:23:37 -0300 Thibault Saunier + + * validate/gst-libs/gst/video/gstvalidatessim.c: + validate:ssim: Flush cairo surface before getting pixels + +2020-02-03 11:14:33 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: Fix reporting on failure when running forever/fatal + +2020-01-14 10:26:54 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/plugins/ssim/gstvalidatessim.c: + validate:ssim: Enhance printing position + Adding a new `gst_validate_print_position` method which also + sends messages to the runner if required. + +2020-01-08 15:26:41 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + * validate/launcher/utils.py: + validate:launcher: Add a generator to generate test for frame accurate seeking + +2020-01-08 15:21:11 -0300 Thibault Saunier + + * validate/plugins/ssim/gstvalidatessim.c: + validate:ssim: Use stream time to reference frames + +2020-01-08 15:18:15 -0300 Thibault Saunier + + * validate/gst-libs/gst/video/gstvalidatessim.c: + validate:ssim: Don't check neighbor frames when comparing exact same frame + +2020-01-08 15:14:34 -0300 Thibault Saunier + + * validate/gst-libs/gst/video/gstvalidatessim.c: + validate:ssim: Enhance debugging message when similarity do not match + Printing out where the diff image file is + +2020-01-07 15:48:51 -0300 Thibault Saunier + + * validate/launcher/utils.py: + validate:launcher: Enhance support for running ssim tests + Using a special 'ssim' variable in pipeline dicts to activate it + Similare to what we do for validateflow + +2020-01-07 15:46:21 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + validate:launcher: Add support for skipped media info files + Those are skipped to generate tests by default but are updated when + required, this will allow us to generate specific test on demand for + those + +2020-01-06 16:29:33 -0300 Thibault Saunier + + * validate/plugins/ssim/gstvalidatessim.c: + validate:ssim: Avoid segfaults trying to attach pads without a template + +2020-01-06 16:26:12 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + validate:scenario:Handle GStreamer serialized timestamps + +2020-02-17 10:32:48 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Never try to load a testsuite with the same name from different locations + +2020-02-13 10:41:07 -0300 Thibault Saunier + + * validate/tools/gst-validate-media-check.c: + validate:media-check: Fix wrong exit code + There are code paths where the runner doesn't contain any issue but an error has already been reported + +2020-02-11 16:01:07 -0300 Thibault Saunier + + * validate/gst/validate/media-descriptor.c: + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + * validate/launcher/utils.py: + validate:launcher: Add support for the imagesequence protocol + +2020-02-03 11:14:33 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Fix reporting on failure when running forever/fatal + +2020-01-08 09:54:15 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Cache GstValidateMediaDescriptor to avoid reparsing .media_info + Saving another second at startup + +2020-01-08 09:23:19 -0300 Thibault Saunier + + * validate/tools/gst-validate-launcher.in: + validate:launcher: Generate profiling data even if an exception happens + +2020-01-07 19:29:05 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + validate:launcher: Batch inspecting scenarios + Removing almost 1 second to start running tests with the default + testsuite + +2020-01-06 16:27:59 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Fix printed test number + +2020-01-06 16:27:19 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: Allow passing configs paths when loading from dictionary + The same way we allow it for scenarios + +2020-03-02 12:55:18 +0000 Philippe Normand + + * debug-viewer/GstDebugViewer/GUI/columns.py: + debug-viewer: Display nanoseconds in the timestamp column + +2020-02-27 16:33:36 +0530 Nirbheek Chauhan + + * validate/plugins/flow/formatting.c: + validate: Don't use sprintf + glib format modifiers + We do not have a way to know the format modifiers to use with string + functions provided by the system. `G_GUINT64_FORMAT` and other string + modifiers only work for glib string formatting functions. We cannot + use them for string functions provided by the stdlib. See: + https://developer.gnome.org/glib/stable/glib-Basic-Types.html#glib-Basic-Types.description + ``` + ../validate/plugins/flow/formatting.c: In function 'format_number': + ../validate/plugins/flow/formatting.c:68:22: error: unknown conversion type character 'l' in format [-Werror=format=] + sprintf (dest_str, "%" G_GUINT64_FORMAT, number); + ^~~ + In file included from /builds/nirbheek/cerbero/cerbero-build/dist/windows_x86_64/include/glib-2.0/glib/gtypes.h:32, + from /builds/nirbheek/cerbero/cerbero-build/dist/windows_x86_64/include/glib-2.0/glib/galloca.h:32, + from /builds/nirbheek/cerbero/cerbero-build/dist/windows_x86_64/include/glib-2.0/glib.h:30, + from /builds/nirbheek/cerbero/cerbero-build/dist/windows_x86_64/include/gstreamer-1.0/gst/gst.h:27, + from ../validate/plugins/flow/formatting.h:26, + from ../validate/plugins/flow/formatting.c:30: + /builds/nirbheek/cerbero/cerbero-build/dist/windows_x86_64/lib/glib-2.0/include/glibconfig.h:69:28: note: format string is defined here + #define G_GUINT64_FORMAT "llu" + ^ + ../validate/plugins/flow/formatting.c:68:22: error: too many arguments for format [-Werror=format-extra-args] + sprintf (dest_str, "%" G_GUINT64_FORMAT, number); + ^~~ + ../validate/plugins/flow/formatting.c:68:22: error: unknown conversion type character 'l' in format [-Werror=format=] + In file included from /builds/nirbheek/cerbero/cerbero-build/dist/windows_x86_64/include/glib-2.0/glib/gtypes.h:32, + from /builds/nirbheek/cerbero/cerbero-build/dist/windows_x86_64/include/glib-2.0/glib/galloca.h:32, + from /builds/nirbheek/cerbero/cerbero-build/dist/windows_x86_64/include/glib-2.0/glib.h:30, + from /builds/nirbheek/cerbero/cerbero-build/dist/windows_x86_64/include/gstreamer-1.0/gst/gst.h:27, + from ../validate/plugins/flow/formatting.h:26, + from ../validate/plugins/flow/formatting.c:30: + /builds/nirbheek/cerbero/cerbero-build/dist/windows_x86_64/lib/glib-2.0/include/glibconfig.h:69:28: note: format string is defined here + #define G_GUINT64_FORMAT "llu" + ^ + ../validate/plugins/flow/formatting.c:68:22: error: too many arguments for format [-Werror=format-extra-args] + sprintf (dest_str, "%" G_GUINT64_FORMAT, number); + ^~~ + ``` + Needed for https://gitlab.freedesktop.org/gstreamer/cerbero/merge_requests/419 + +2020-02-26 14:21:52 -0300 Thibault Saunier + + * validate/gst/validate/meson.build: + * validate/meson.build: + * validate/win32/common/libgstvalidate.def: + meson: remove vs_module_defs + The GST_EXPORT should handle it. + +2020-02-26 12:05:39 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + launcher: Do not dump output on known issues + And remove dead code + +2020-02-26 10:52:12 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + validate: Fix leak parsing structure files + +2020-02-25 11:00:57 -0300 Thibault Saunier + + * meson.build: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-reporter.h: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/gst/validate/gst-validate-utils.c: + validate: Enhance error reporting for errors in struct files + Get a sense of files and line numbers in the parsed GstStructure + and take that information when reporting GstValidateAction errors + by letting the user know where the action comes from in the messages. + And accept non-literal string in printing formats. + +2020-02-25 10:54:00 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + validate: Add a specific error type for check actions + And mark it as `NO_BACKTRACE | FULL_DETAILS`, same as for + other action failure types. + +2020-01-16 17:36:54 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + * validate/launcher/baseclasses.py: + validate: Enhance error reporting when scenario or configs are invalid + +2020-01-14 10:23:39 -0300 Thibault Saunier + + * validate/gst-libs/gst/video/gstvalidatessim.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-reporter.c: + * validate/win32/common/libgstvalidate.def: + validate: Add a flags to issues + Currently those allow registering issue that: + - Won't print backtrace as it is sometimes useless info + - Will repeat the details even in smart mode + +2020-02-19 22:16:44 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + validate: Consider ',' as continuing line marker in struct files + Since `,` is the separator between fields of GstStructure we can + safely consider that if a line ends with it, the following line + is the logical continuity of the serialized GstStructure. + This makes writing those files more convenient and reading them + more pleasant as we do not need to add extra `\` at end of lines + anymore + +2020-02-11 09:18:23 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Handle validate report bigger than allowed by the socket + This almost never happens but I had a case where we had a report + with a GstSample in the caps that were reported leading to an + error printed. + +2020-02-06 10:34:40 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Reference exception in a var as we use it in the handling + +2020-02-04 18:14:25 -0300 Thibault Saunier + + * docs/plugins/validateflow.md: + * validate/plugins/flow/formatting.c: + * validate/plugins/flow/formatting.h: + * validate/plugins/flow/gstvalidateflow.c: + validate:flow: Make field filtering in what is logged more generic + Instead of forcing it on event, allow specifying filters on anything + we log, meaning also buffers + +2020-02-04 16:59:39 -0300 Thibault Saunier + + * docs/plugins/validateflow.md: + * validate/plugins/flow/formatting.c: + * validate/plugins/flow/formatting.h: + * validate/plugins/flow/gstvalidateflow.c: + validateflow: Add a logged-event-fields configuration + +2020-02-11 15:57:56 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Stop rounding up clocktime values + This doesn't make any sense in that context + +2020-02-06 14:33:56 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: Fix defining several scenario with a same config + When generating tests from dictionary the dict format allows passing + several scenario for a same config and pipelines, but this was breaking + the case where expected flow is different with each config, instead we + should generate one config per scenario, fixing the expectation files + generated. + +2020-01-15 21:22:49 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Print the duration of the test run in the logs + +2020-01-15 21:15:30 -0300 Thibault Saunier + + validate:flow: Add a way to set the types of events to log/ignore + Added two properties to the plugin: + * ignored-event-types: A list of event types to be ignored when logging events + * logged-event-types: A list of event types to be logged when logging events + This commits also moves the "ignored-event-fields" property to using a proper + GstValueList for the list of event fields to be taken into account, instead + of the home grown separated by comas list of string, making the API more + uniform. + This also adds a simple helper method: `gst_validate_utils_get_strv` + +2019-12-03 18:26:18 +0100 Stéphane Cerveau + + * validate/launcher/baseclasses.py: + * validate/launcher/reporters.py: + gst-validate-launcher: separate known error from passed tests + Introduce known_error in statistics to keep in mind the expected + error result. + +2019-12-02 14:46:59 +0100 Stéphane Cerveau + + * docs/gst-validate-launcher.md: + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/main.py: + gst-validate-launcher: update documentation + Use the new api to create your custom testsuite. + Fix some broken links and enhance the logging system. + +2020-01-11 23:00:06 -0500 Nicolas Dufresne + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + launcher: Allow partionning the tests + This introduce new command line options, --parts and --part-index. When + --parts is set to a value larger then 1, the tests will be split in the + same number of group. The group number identified by --part-index will + be executed. + This is being added in orther to support gliblab CI parallel feature. + +2019-08-12 16:56:41 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + validate:launcher: Try to dump logs with bat if avalaible + We got to many issues with mdv, it seems not to be maintained + bat is a very good replacement. + +2020-01-05 14:09:07 -0600 Brady J. Garvin + + * validate/launcher/loggable.py: + validate:launcher: Support mixed str/bytes control sequences. + It is not safe for `_preformat_levels` to assume that all of the fields in a + `TerminalController` have the same type; at least in my environment, some of + these fields are populated with `bytes` while others remain strings. + This change conditionally applies decoding to each control sequence separately + using a helper function `_as_string`. As a side-effect, it also eliminates some + code repetition in `_preformat_levels`. + Closes #50. + +2019-12-30 12:57:57 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Enhance progress reporting using a progress bar + This also allows us to properly report progress on the CI + +2019-12-30 10:27:06 -0300 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + validate:launcher: Take our timeout factor into account for gstcheck + +2019-11-07 15:57:41 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: fix some typos + +2019-11-07 15:55:17 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Implement an action to check property value + +2019-12-28 22:39:23 +0100 Alexandru Băluț + + * meson_options.txt: + meson: Remove unused gtk_doc option + The "doc" option is available to disable the documentation. + +2019-12-23 10:27:11 +0100 Mathieu Duponchelle + + * validate/launcher/testsuites/check.py: + check: unblacklist removed systemclock tests + See https://gitlab.freedesktop.org/gstreamer/gstreamer/merge_requests/348 + +2019-12-14 10:45:30 +0100 Mathieu Duponchelle + + * validate/launcher/testsuites/check.py: + check: unblacklist gstreamer.pipelines_parse_launch.delayed_link + It should not be flaky anymore after + https://gitlab.freedesktop.org/gstreamer/gstreamer/merge_requests/343 + +2019-11-15 17:25:11 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: Use python dict for pipeline description + +2019-11-27 15:33:14 +0100 Edward Hervey + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: Initialize variable + We could end up using it uninitialized + CID: 1444920 + +2019-10-04 09:59:57 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + validate: launcher: Make encoding extra check use common code path + Reusing the reporting infrastructure instead of shurtcuting it + +2019-09-24 14:23:49 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + validate: Add vp9 transcoding tests + Making sure to encode small frames as vp9enc is slow. + +2019-11-20 10:19:00 +0100 Edward Hervey + + * validate/gst/validate/validate.c: + * validate/plugins/flow/formatting.c: + * validate/tests/check/validate/scenario.c: + validate: Fix memory leaks + Various structures were being leaked. + +2019-11-11 18:57:27 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Avoid clashes when importing testsuite + This introduce an hard dependency on python >= 3.5, same as meson + +2019-11-10 16:29:45 +0100 Jordan Petridis + + * validate/launcher/testsuites/check.py: + check: blacklist gst-plugins-good.elements_splitmux.test_splitmuxsink$ + https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/issues/626 + +2019-11-06 18:21:11 +0100 Stéphane Cerveau + + * validate/launcher/apps/gstvalidate.py: + gstvalidate: fix GstValidateRTSPMediaDescriptor typo + +2019-11-02 17:07:02 +0100 Alicia Boya García + + * validate/gst/validate/gst-validate-bin-monitor.h: + gst-validate-bin-monitor: Remove unused field. + +2019-10-25 12:45:40 +0200 Alicia Boya García + + * validate/plugins/flow/gstvalidateflow.c: + validateflow: Don't use colon in file names + The colon character commonly used to separate the element name and the + pad name is reserved in Windows filesystems, so it's better to use + something safer. + This patch replaces it with '-'. Please update + gst-integration-testsuites too where another commit has renamed all the + files. + +2019-10-25 12:26:58 +0200 Víctor Manuel Jáquez Leal + + * validate/launcher/testsuites/check.py: + validate: blacklist gstreamer-vaapi checks + They still can be checked by running the tests explicitly. + +2019-10-18 12:31:19 +0100 Tim-Philipp Müller + + * meson.build: + meson: build gir even when cross-compiling if introspection was enabled explicitly + This can be made to work in certain circumstances when + cross-compiling, so default to not building g-i stuff + when cross-compiling, but allow it if introspection was + enabled explicitly via -Dintrospection=enabled. + See gstreamer/gstreamer#454 and gstreamer/gstreamer#381. + +2019-10-15 00:24:00 +0100 Tim-Philipp Müller + + * .gitignore: + * .gitmodules: + * configure: + * validate/.gitignore: + * validate/Makefile.am: + * validate/README: + * validate/autogen.sh: + * validate/common: + * validate/configure.ac: + * validate/data/Makefile.am: + * validate/data/scenarios/Makefile.am: + * validate/docs/.gitignore: + * validate/gst-libs/Makefile.am: + * validate/gst-libs/gst/Makefile.am: + * validate/gst-libs/gst/video/Makefile.am: + * validate/gst/Makefile.am: + * validate/gst/overrides/Makefile.am: + * validate/gst/validate/Makefile.am: + * validate/launcher/Makefile.am: + * validate/launcher/apps/Makefile.am: + * validate/launcher/testsuites/Makefile.am: + * validate/pkgconfig/Makefile.am: + * validate/plugins/Makefile.am: + * validate/plugins/fault_injection/Makefile.am: + * validate/plugins/flow/Makefile.am: + * validate/plugins/gapplication/Makefile.am: + * validate/plugins/gtk/Makefile.am: + * validate/plugins/ssim/Makefile.am: + * validate/po/Makevars: + * validate/po/POTFILES.in: + * validate/tests/Makefile.am: + * validate/tests/check/Makefile.am: + * validate/tools/.gitignore: + * validate/tools/Makefile.am: + * validate/win32/MANIFEST: + validate: remove autotools build + +2019-10-15 00:11:19 +0100 Tim-Philipp Müller + + * codecanalyzer/.gitignore: + * codecanalyzer/AUTHORS: + * codecanalyzer/COPYING: + * codecanalyzer/Makefile.am: + * codecanalyzer/NEWS: + * codecanalyzer/README.md: + * codecanalyzer/autogen.sh: + * codecanalyzer/configure.ac: + * codecanalyzer/data/Makefile.am: + * codecanalyzer/data/pixmaps/Makefile.am: + * codecanalyzer/data/pixmaps/codecanalyzer-logo.png: + * codecanalyzer/data/pixmaps/frame-thumbnail.png: + * codecanalyzer/data/ui/LICENSE.txt: + * codecanalyzer/data/ui/Makefile.am: + * codecanalyzer/data/ui/mainwindow.xml: + * codecanalyzer/data/ui/menu.xml: + * codecanalyzer/src/Makefile.am: + * codecanalyzer/src/codecanalyzer.c: + * codecanalyzer/src/gst_analyzer.c: + * codecanalyzer/src/gst_analyzer.h: + * codecanalyzer/src/plugins/Makefile.am: + * codecanalyzer/src/plugins/gst/Makefile.am: + * codecanalyzer/src/plugins/gst/analyzersink/Makefile.am: + * codecanalyzer/src/plugins/gst/analyzersink/analyzer_utils.c: + * codecanalyzer/src/plugins/gst/analyzersink/analyzer_utils.h: + * codecanalyzer/src/plugins/gst/analyzersink/gstanalyzersink.c: + * codecanalyzer/src/plugins/gst/analyzersink/gstanalyzersink.h: + * codecanalyzer/src/plugins/gst/analyzersink/mpeg_xml.c: + * codecanalyzer/src/plugins/gst/analyzersink/mpeg_xml.h: + * codecanalyzer/src/plugins/gst/analyzersink/plugin.c: + * codecanalyzer/src/plugins/gst/analyzersink/xml_utils.c: + * codecanalyzer/src/plugins/gst/analyzersink/xml_utils.h: + * codecanalyzer/src/xml_parse.c: + * codecanalyzer/src/xml_parse.h: + codecanalyzer: remove + Remove in effort to declutter. There has been + pretty much no activity at all since the initial + commit in 2014 apart from a few coverity fixes. + Doesn't seem super-useful in its current form + either. Still available on github at + https://github.com/sreerenjb/codecanalyzer/ + if anyone has a use for it. + +2019-10-14 19:28:08 +0100 Tim-Philipp Müller + + * mediainfo/AUTHORS: + * mediainfo/COPYING: + * mediainfo/ChangeLog: + * mediainfo/HACKING: + * mediainfo/Makefile.am: + * mediainfo/NEWS: + * mediainfo/README: + * mediainfo/TODO: + * mediainfo/autogen.sh: + * mediainfo/configure.ac: + * mediainfo/git.mk: + * mediainfo/gst-mediainfo.anjuta: + * mediainfo/po/LINGUAS: + * mediainfo/po/POTFILES.in: + * mediainfo/po/POTFILES.skip: + * mediainfo/src/Makefile.am: + * mediainfo/src/gst-mi.desktop.in: + * mediainfo/src/gst-mi.png: + * mediainfo/src/gst-mi.svg: + * mediainfo/src/mi-app.vala: + * mediainfo/src/mi-info.vala: + * mediainfo/src/mi-preview.vala: + * mediainfo/src/mi.vala: + * mediainfo/vapi/Makefile.am: + * mediainfo/vapi/config.vapi: + mediainfo: remove + This looks bitrotten and abandoned, + remove in an effort to declutter. + +2019-10-14 19:25:30 +0100 Tim-Philipp Müller + + * vagrant/Vagrantfile: + * vagrant/ansible_hosts: + * vagrant/gst-streaming-server-git.yml: + * vagrant/gstreamer-git.yml: + * vagrant/gstreamer.yml: + * vagrant/ipython.yml: + * vagrant/playbook.yml: + vagrant: remove + This looks bitrotten and still targets Ubuntu 13.10 + and autotools. Doesn't look like anyone has been using + this in the last 5+ years, so let's remove it in an + effort to declutter. + +2019-07-15 17:58:05 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Fix the 'can-happen-several-times' known issue field + +2019-07-15 12:36:23 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.h: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + validate: Check that pull_range is called from the streaming thread + `gst_pad_pull_range` should always be called from the streaming thread, + we now check that when pull_range is called, and if the sinkpad calling + the function has a GstTask with a running thread, the function is called + from that thread. + +2019-09-27 16:52:51 -0400 Nicolas Dufresne + + * validate/gst/validate/gst-validate-scenario.c: + validate-scanario: Fix crash when using installed validate + When installed, the lookup path will endup on the very last try, but the + scenario_file was left unset, which lead to a crash. + +2019-09-24 11:45:34 +1000 Matthew Waters + + * validate/gst/validate/gst-validate-scenario.c: + validate: fix build with newer gcc + In file included from ../../../../dist/linux_x86_64/include/gstreamer-1.0/gst/gst.h:55, + from ../validate/gst/validate/gst-validate-scenario.c:45: + ../validate/gst/validate/gst-validate-scenario.c: In function ‘gst_validate_scenario_load’: + ../../../../dist/linux_x86_64/include/gstreamer-1.0/gst/gstinfo.h:645:5: error: ‘%s’ directive argument is null [-Werror=format-overflow=] + 645 | gst_debug_log ((cat), (level), __FILE__, GST_FUNCTION, __LINE__, \ + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 646 | (GObject *) (object), __VA_ARGS__); \ + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ../../../../dist/linux_x86_64/include/gstreamer-1.0/gst/gstinfo.h:1067:26: note: in expansion of macro ‘GST_CAT_LEVEL_LOG’ + 1067 | #define GST_ERROR(...) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, GST_LEVEL_ERROR, NULL, __VA_ARGS__) + | ^~~~~~~~~~~~~~~~~ + ../validate/gst/validate/gst-validate-scenario.c:3615:5: note: in expansion of macro ‘GST_ERROR’ + 3615 | GST_ERROR ("Invalid name for scenario '%s'", scenario_name); + | ^~~~~~~~~ + ../validate/gst/validate/gst-validate-scenario.c:3615:44: note: format string is defined here + 3615 | GST_ERROR ("Invalid name for scenario '%s'", scenario_name); + | ^~ + +2019-08-24 07:57:23 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Do not hardcode pathsep + +2019-08-28 17:22:55 +1000 Matthew Waters + + * validate/plugins/flow/gstvalidateflow.c: + validate/flow: fix werror build with android clang + ../validate/plugins/flow/gstvalidateflow.c:75:3: error: redefinition of typedef 'ValidateFlowOverride' is a C11 feature [-Werror,-Wtypedef-redefinition] + } ValidateFlowOverride; + ^ + ../validate/plugins/flow/gstvalidateflow.h:31:23: note: previous definition is here + G_DECLARE_FINAL_TYPE (ValidateFlowOverride, validate_flow_override, + ^ + +2019-08-26 21:43:24 +1000 Matthew Waters + + * validate/plugins/flow/gstvalidateflow.c: + * validate/plugins/flow/gstvalidateflow.h: + validate: fix -Werror=unused-function with clang + [3623/4053] Compiling C object 'subprojects/gst-devtools/validate/plugins/flow/697521d@@gstvalidateflow@sha/gstvalidateflow.c.o'. + ../subprojects/gst-devtools/validate/plugins/flow/gstvalidateflow.c:85:1: warning: unused function 'VALIDATE_IS_FLOW_OVERRIDE' [-Wunused-function] + G_DECLARE_FINAL_TYPE (ValidateFlowOverride, validate_flow_override, + ^ + /usr/include/glib-2.0/gobject/gtype.h:1407:26: note: expanded from macro 'G_DECLARE_FINAL_TYPE' + static inline gboolean MODULE##_IS_##OBJ_NAME (gpointer ptr) { \ + ^ + :129:1: note: expanded from here + VALIDATE_IS_FLOW_OVERRIDE + ^ + +2019-08-26 21:41:00 +1000 Matthew Waters + + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-reporter.h: + * validate/plugins/flow/gstvalidateflow.c: + validate: fix -Werror=format-nonliteral build with clang + [3470/4053] Compiling C object 'subprojects/gst-devtools/validate/gst/validate/28db7b6@@gstvalidatetracer@sha/gst-validate-reporter.c.o'. + ../subprojects/gst-devtools/validate/gst/validate/gst-validate-reporter.c:186:31: warning: format string is not a string literal [-Wformat-nonliteral] + message = g_strdup_vprintf (format, vacopy); + ^~~~~~ + [3487/4053] Compiling C object 'subprojects/gst-devtools/validate/gst/validate/28db7b6@@gstvalidatetracer@sha/gst-validate-report.c.o'. + ../subprojects/gst-devtools/validate/gst/validate/gst-validate-report.c:1007:34: warning: format string is not a string literal [-Wformat-nonliteral] + tmp = gst_info_strdup_vprintf (format, args); + ^~~~~~ + [76/151] Compiling C object 'subprojects/gst-devtools/validate/plugins/flow/697521d@@gstvalidateflow@sha/gstvalidateflow.c.o'. + ../subprojects/gst-devtools/validate/plugins/flow/gstvalidateflow.c:125:65: warning: format string is not a string literal [-Wformat-nonliteral] + if (!flow->error_writing_file && vfprintf (flow->output_file, format, ap) < 0) { + ^~~~~~ + +2019-08-19 12:25:39 +0100 Philippe Normand + + * validate/data/scenarios/meson.build: + * validate/data/scenarios/play_5s.scenario: + validate: Add a scenario for 5 seconds playback use-cases + +2019-08-19 11:08:41 +0100 Philippe Normand + + * validate/plugins/ssim/gstvalidatessim.c: + validate/ssim: Clean-up temporary directory + When no output-dir is specified in the plugin config, a temporary directory is + created, so it needs to be removed when no-longer needed. + +2019-08-19 11:25:45 +0100 Philippe Normand + + * validate/launcher/apps/gstvalidate.py: + validate/launcher: Ensure the HTTP server is started when a pipeline needs it + Pipelines declared in gst-integration-testsuites can rely on the validate HTTP + server, so when an URI pointing to it is detected, advertise the server as + needed before starting the test. + For this to work the test scenario should explicitely declare the pipeline uri, + as shown in this example: + "some_playbin3": + { + "pipeline": "playbin3 uri=%(uri)s video-sink=%(videosink)s", + "config": [ + "%(validateflow)s, pad=sink:sink" + ], + "scenarios": ["play_15s"], + "uri": "http://127.0.0.1:%(http-server-port)s/defaults/html/foo.html" + } + +2019-08-05 19:04:54 -0400 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: Allow passing any extra_data in json test definition + This means that we can now pass any extra key that `populate_tests` + expects, meaning any key expected by FakeMediaDescriptor and + a few other keys supported by the methods such as + `expected-issues` and `extra_env_vars` + +2019-07-10 16:52:45 -0400 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + launcher: Raise an exception when provided scenario can't be found + +2019-08-03 20:09:32 -0400 Aaron Boxer + + * validate/gst/validate/validate.h: + validate: add missing G_BEGIN/END_DECLS in validate.h + +2019-08-01 21:04:12 +0200 Mathieu Duponchelle + + * validate/launcher/apps/gstvalidate.py: + validate: Update blacklisting reason for fast forward rtsp + While https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/issues/14 + was merged, the client side (in particular rtpbasedepayload) still + isn't expected to work appropriately + +2019-07-29 10:05:20 +0100 Tim-Philipp Müller + + * validate/gst/validate/gst-validate-scenario.c: + validate: fix build with older GLib versions + g_enum_to_string() is only available in newer ones. + Add compatibility workaround for the time being to decouple + this from the decision whether to bump the GLib requirement + and what to bump it to. + https://gitlab.freedesktop.org/gstreamer/gstreamer/merge_requests/199 + Fixes #45 + +2019-07-26 02:28:42 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-scenario.c: + scenario: fix PAUSED position check + The way this was implemented was simply wrong, first of all checking + the position against the segment after a seek in PAUSED by a query + of the pipeline position is of limited interest, and can only work + in forward playback. + Furthermore the check was a huge blob of code that didn't even look + like it was pretending to do a good job at checking the position in + reverse playback. + +2019-07-26 02:26:20 +0200 Mathieu Duponchelle + + * validate/launcher/apps/gstvalidate.py: + json tests: assume all user pipelines can seek reverse + When doing a targeted test, it is up to the user to make sure + their pipeline + scenario behaves correctly. + +2019-07-08 23:37:22 -0400 Thibault Saunier + + * validate/launcher/main.py: + validate:launcher: Pass the right timeout_factor is passed to subprojects + +2019-07-08 23:36:52 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Do not dereference NULL pointer + +2019-07-03 15:42:26 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + validate:pipeline-monitor: Connect deep properties notification only when required + This is quite expensive and can lead to an overwhelm mainloop. + +2019-06-28 17:34:00 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario: Keep a reference to 'description' structure + For it to be reusable outside the scenario + +2019-06-27 00:41:03 +0000 Thibault Saunier + + * docs/gst-validate-config.md: + doc: Minor typo fixes. + +2019-06-23 13:56:52 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Use internal sinks when a sink bin can be used to check last-sample + +2019-06-23 13:40:37 -0400 Thibault Saunier + + * docs/gst-validate-config.md: + * docs/sitemap.txt: + docs: Document validate core configuration + +2019-06-23 12:48:43 -0400 Thibault Saunier + + * validate/plugins/flow/gstvalidateflow.c: + validate:flow: Log buffers even when tracking srcpads + +2019-06-23 12:48:06 -0400 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + * validate/launcher/utils.py: + validate:launcher: Move get_fakesink_for_media_type to utils + So it can be reused in other apps like GES + +2019-06-23 12:46:37 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-element-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.c: + validate: Also monitor ghost pads + Allowing overrides to work on ghost pads too + +2019-06-23 11:58:11 -0400 Thibault Saunier + + * docs/plugins/validateflow.md: + * validate/plugins/flow/formatting.c: + * validate/plugins/flow/formatting.h: + * validate/plugins/flow/gstvalidateflow.c: + validateflow: Add `buffers-checksum` option to log buffers data checksum + +2019-06-23 03:31:30 -0400 Thibault Saunier + + * docs/gst-validate-config.md: + * docs/gst-validate-scenarios.md: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/validate.c: + validate: Add SCENARIO_NAME and CONFIG_NAME vars in configs/scenarios + +2019-06-23 03:09:58 -0400 Thibault Saunier + + * docs/gst-validate-config.md: + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/gst-validate-utils.h: + * validate/gst/validate/validate.c: + * validate/launcher/baseclasses.py: + validate: Set 'LOGSDIR' variable in scenarios and config files + Implementing support for variables in config files. + +2019-06-23 01:51:08 -0400 Thibault Saunier + + * docs/gst-validate-scenarios.md: + * validate/gst/validate/gst-validate-scenario.c: + scenario: Set `SCENARIO_PATH/DIR` variables in scenarios + And add some documentation about it + +2019-06-23 00:47:04 -0400 Thibault Saunier + + * docs/gst-validate-scenarios.md: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/gst-validate-utils.h: + scenario: Add a `TMPDIR` global variables in scenarios + This also adds the notion of global variables which will be useable + in config files too. + And add some documentation about default variables in scenarios + +2019-06-18 18:10:24 -0400 Thibault Saunier + + * docs/gst-validate-action-types.md: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-scenario.c: + docs: Update validate action types + Include minor fixes in the action types and markdown generator + +2019-06-18 17:43:56 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/media-descriptor-writer.c: + * validate/tools/gst-validate.c: + validate: Misc leaks plugging + +2019-06-17 17:59:21 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + validate:launcher: Strip env vars in command line outputing verbose + But activate if activating verbosity more than once + +2019-06-14 10:43:15 -0400 Thibault Saunier + + * docs/ges-validate-action-types.md: + * docs/gst-validate-action-types.md: + * docs/sitemap.txt: + docs: Document GES validate action types + +2019-06-11 17:11:42 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + validate:reporter: Show report by branches when doing smart reporting + Meaning that instead of getting 1 "Detected on" line per monitor, + there will be one per "branch" like: + Detected on + Making it simpler to read and a bit less verbose. + +2019-06-11 16:34:28 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Avoid repeating failure info in summaries + +2019-06-11 16:27:55 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/tools/gst-validate-transcoding.c: + validate:scenario: Move force-key-unit action from the transcoding tool + The action is generally useful but was implemented in a way that + was restricting its usage for no good reason. Refactor the + implementation adding more argument so it can be used in a wider + context, such as uvch264src. + Something like: + ``` bash + echo "video-request-key-unit, direction=upstream, all-header=true, count=1, target-element-factory-name=h264parse, srcpad=src, playback-time=1.0" > tmp.scenario && \ + echo "stop,playback-time=2.0" >> tmp.scenario && \ + gst-validate-1.0 --set-scenario=tmp.scenario uvch264src \ + device=/dev/video0 name=src iframe-period=33 auto-start=true src.vfsrc ! queue ! fakesink \ + src.vidsrc ! queue ! video/x-h264,width=1280,height=720,framerate=30/1 ! h264parse ! fakesink + ``` + works now. + +2019-06-11 16:23:00 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Minor documentation cleanup + +2019-06-11 16:21:52 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + validate:pipeline-monitor: Avoid wrong position issue + If the reported position or duration is NONE, do not check its + validity + +2019-05-31 23:18:08 +0200 Niels De Graef + + * meson.build: + meson: Bump minimal GLib version to 2.44 + This means we can use some newer features and get rid of some + boilerplate code using the G_DECLARE_* macros. + As discussed on IRC, 2.44 is old enough by now to start depending on it. + +2019-05-21 14:54:39 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-scenario.c: + validate: Implement seeking with DEFAULT format + +2019-05-23 11:49:01 -0400 Thibault Saunier + + * validate/plugins/ssim/gstvalidatessim.c: + ssim: Report critical issue when override not attached + +2019-05-23 11:35:28 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-override-registry.c: + * validate/plugins/ssim/gstvalidatessim.c: + ssim: Fix the way we handle when an override is attached + +2019-05-23 11:34:19 -0400 Thibault Saunier + + * docs/plugins/ssim.md: + docs: Minor fix about ssim plugin + +2019-04-24 16:24:05 +0000 Thibault Saunier + + * docs/gst-validate-transcoding.md: + * docs/index.md: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/validate.c: + docs: Fix docstrings + +2019-04-20 10:21:07 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + validate:launcher: Do not dump to big log files + Avoiding ' The script exceeded the maximum execution time set for the job' in GitLab + +2019-02-07 15:50:26 -0300 Thibault Saunier + + * docs/sitemap.txt: + docs: Document the validateflow plugin + +2018-11-17 16:50:30 -0300 Thibault Saunier + + * docs/gst-validate-action-types.md: + * docs/sitemap.txt: + docs: Document validate action types + Just did: + ``` + gst-validate-1.0 --inspect-action-type all > docs/gst-validate-action-types.md + ``` + +2018-11-17 12:31:13 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/tools/gst-validate.c: + validate: inspect: Output valid markdown + So it can be used directly in the documentation Also add a special "all" + argument to `gst-validate-1.0 --inspect-action-type` so we can generate + the documentation for all action types easily. + +2018-10-22 11:38:30 +0200 Thibault Saunier + + * docs/api.md: + * docs/gi-index.md: + * docs/gst-validate-config.md: + * docs/gst-validate-environment-variables.md: + * docs/gst-validate-launcher.md: + * docs/gst-validate-media-check.md: + * docs/gst-validate-scenarios.md: + * docs/gst-validate-transcoding.md: + * docs/gst-validate.md: + * docs/index.md: + * docs/meson.build: + * docs/plugins/index.md: + * docs/plugins/ssim.md: + * docs/sitemap.txt: + * meson.build: + * meson_options.txt: + * validate/Makefile.am: + * validate/configure.ac: + * validate/docs/Makefile.am: + * validate/docs/launcher/Makefile.am: + * validate/docs/launcher/conf.py: + * validate/docs/launcher/index.rst: + * validate/docs/launcher/launcher.rst: + * validate/docs/launcher/modules.rst: + * validate/docs/meson.build: + * validate/docs/plugins/.gitignore: + * validate/docs/plugins/Makefile.am: + * validate/docs/plugins/gst-validate-plugins-docs.sgml: + * validate/docs/plugins/gst-validate-plugins-overrides.txt: + * validate/docs/plugins/gst-validate-plugins-sections.txt: + * validate/docs/plugins/gst-validate-plugins.sgml: + * validate/docs/plugins/gst-validate-plugins.types: + * validate/docs/validate/.gitignore: + * validate/docs/validate/Makefile.am: + * validate/docs/validate/envvariables.xml: + * validate/docs/validate/gst-validate-docs.sgml: + * validate/docs/validate/gst-validate-launcher.xml: + * validate/docs/validate/gst-validate-media-check.xml: + * validate/docs/validate/gst-validate-sections.txt: + * validate/docs/validate/gst-validate-transcoding.xml: + * validate/docs/validate/gst-validate.types: + * validate/docs/validate/gst-validate.xml: + * validate/docs/validate/meson.build: + * validate/docs/validate/scenarios.xml: + * validate/docs/version.entities.in: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-scenario.h: + * validate/gst/validate/media-descriptor-writer.h: + * validate/gst/validate/meson.build: + * validate/meson.build: + * validate/plugins/ssim/gstvalidatessim.c: + doc: Port to hotdoc + Ideally we want a GstValidate hotdoc plugin... not for now. + +2018-10-22 11:38:27 +0200 Thibault Saunier + + * validate/tests/check/meson.build: + meson: Use dep.type_name() when it makes sense + +2018-10-22 11:38:24 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-enums.h: + * validate/gst/validate/gst-validate-monitor-factory.c: + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/validate.c: + doc: Minor fixes + +2019-04-19 13:02:43 +0100 Tim-Philipp Müller + + * validate/launcher/testsuites/check.py: + launcher: testsuites: skip systemclock stress tests + These are very flaky when the build bots are under load. + +2019-05-04 19:54:16 +0100 Tim-Philipp Müller + + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/validate.c: + validate: fix build on macOS + _Q_VALIDATE_MONITOR was defined twice because it wasn't declared + as extern in the header, so it would be defined as variable in all + included files. This doesn't seem to cause problems on Linux, but + seems to cause build failures on macOS. + Fixes #42 + +2019-04-19 10:42:30 +0100 Tim-Philipp Müller + + * meson.build: + * validate/RELEASE: + * validate/configure.ac: + * validate/meson.build: + Back to development + +=== release 1.16.0 === + +2019-04-19 00:36:53 +0100 Tim-Philipp Müller + + * meson.build: + * validate/ChangeLog: + * validate/NEWS: + * validate/RELEASE: + * validate/configure.ac: + * validate/gst-validate.doap: + * validate/meson.build: + Release 1.16.0 + +2019-04-17 17:05:36 -0400 Thibault Saunier + + * validate/launcher/testsuites/check.py: + validate:check: Mare nle test_simple_operation as long + +2019-04-17 16:46:31 -0400 Thibault Saunier + + * validate/launcher/testsuites/check.py: + validate:check: Fix some mistakes translating regex from gitlab-ci.yml + +2019-04-17 16:59:32 -0400 Thibault Saunier + + * .gitlab-ci.yml: + ci: Run valgrind tests when changing the check testsuite + +2019-04-17 11:04:45 -0400 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + validate:launcher: Set ORC_CODE=backup when running gst unit tests under valgrind + +2019-04-17 10:43:09 -0400 Thibault Saunier + + * validate/launcher/testsuites/check.py: + validate:check: Blacklist and mark some GES tests as long under valgrind + +2019-04-17 10:30:02 -0400 Thibault Saunier + + * validate/launcher/testsuites/check.py: + validate:testsuites: Add unit tests blacklists + +2019-04-17 08:56:46 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Ignore possibly lost for now + +2019-04-17 02:31:30 +0200 Mathieu Duponchelle + + * validate/launcher/baseclasses.py: + TestsManager: _add_blacklist in set_default_blacklist + Otherwise test suites that want to set_default_blacklist and + add tests in setup_tests were seeing their blacklist ignored. + Split up and rename set_blacklists() to complete the refactoring + +2019-04-17 02:29:36 +0200 Mathieu Duponchelle + + * validate/launcher/baseclasses.py: + Check indirect leaks + +2019-04-15 16:13:48 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Do not copy logs for non flaky tests + That looks weird for users and is incorrect + +2019-04-15 11:50:16 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Fix printing debug logs URIs + +2019-04-12 12:33:25 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Do not stop tcp server when reiterating tests runs + +2019-04-12 10:13:15 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Associate issues with the bug they come from + Making it simpler to follow when print the known issues + +2019-04-12 10:12:45 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Indent known issues printing + +2019-04-11 10:31:07 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Print the number of times the test was run when --forever + +2019-04-06 11:40:32 -0300 Thibault Saunier + + * validate/launcher/utils.py: + validate:launcher: Add python suppression files + +2019-04-06 11:10:14 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + validate:launcher: Use md viewer to dump md logs if avalaible + And enhance the markdown + See https://github.com/axiros/terminal_markdown_viewer + +2019-04-06 10:46:52 -0300 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + validate:launcher: Avoid forking when running gstcheck tests in gdb + +2019-04-05 23:05:20 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + validate:launcher: Add an option to output HTML if commonmark is installed + +2019-04-05 22:23:29 -0300 Thibault Saunier + + * validate/launcher/utils.py: + validate:launcher: Do not user python 3.5 features + +2019-04-05 10:41:14 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Move all logs to one single log files to be displayed to end user + +2019-04-05 10:40:45 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + validate:launcher: Make extra_logfiles a set and fix their names + +2019-04-05 10:40:04 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: Remove spurious print + +2019-04-04 17:07:58 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Remove noise about empty known issues in logs + +2019-04-04 17:05:14 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Fix printing application name in the logs + +=== release 1.15.90 === + +2019-04-11 01:27:45 +0100 Tim-Philipp Müller + + * meson.build: + * validate/ChangeLog: + * validate/NEWS: + * validate/RELEASE: + * validate/configure.ac: + * validate/gst-validate.doap: + * validate/meson.build: + Release 1.15.90 + +2019-04-11 01:25:17 +0100 Tim-Philipp Müller + + * validate/gst/validate/Makefile.am: + validate: autotools: dist mock decryptor header file + +2019-04-11 01:00:39 +0100 Tim-Philipp Müller + + * validate/win32/common/libgstvalidate.def: + validate: win32: add new api to export file + +2019-03-28 10:08:16 -0300 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + * validate/launcher/baseclasses.py: + * validate/launcher/utils.py: + validate:launcher: Add a list of well known subpression files from gst-build subprojects + Fixes https://gitlab.freedesktop.org/gstreamer/gst-devtools/issues/38 + +2019-03-27 12:36:16 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + validate: Add a mecanism to rerun failling tests + And add a way to mark some 'flakes' as tolerated + +2019-03-27 15:07:48 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Reset process on clean + +2019-03-26 19:35:19 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + validate: Print error message details in our reports + +2019-03-26 15:18:27 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Dump applied known issues in the logs + +2019-03-26 14:09:54 -0300 Thibault Saunier + + * validate/launcher/reporters.py: + validate:launcher: Put all logs inside the failure node + Pleasing gitlab CI reporting system + +2019-03-26 12:19:16 -0300 Thibault Saunier + + * validate/launcher/main.py: + validate:launcher: By default use cpu_count / 2 + +2019-03-26 10:49:26 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Print name of the test to be debugged + When --debug was passed + +2019-03-26 10:47:12 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/tests/check/validate/padmonitor.c: + validate:pad-monitor: Accept any return value when we aggregated FLUSHING while tearing down + Basically nothing guarantees that the set of pads we aggregated the flow + for is the same as the one that was aggregated during the actual data + flow as some pads could have been removed meanwhile. + +2019-03-25 18:14:53 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Allow last-sample to not be set when checking it + And instead of failling, wait for it to be set again. + Depends on https://gitlab.freedesktop.org/gstreamer/gstreamer/merge_requests/119 + +2019-03-25 18:14:35 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate: Return the GstValidateAction on .ref() + +2019-03-25 16:46:46 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/launcher/baseclasses.py: + validate: Generate dot files and link to them on CI + +2019-03-21 17:10:25 -0300 Thibault Saunier + + * validate/launcher/utils.py: + validate:launcher: Try to send SIGINT before killing processes + Giving a chance for validate to print reports + +2019-03-21 10:01:14 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Add a way to say that a known issues can happen several times + +2019-03-19 21:39:02 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Remove now useless code to check sending EOS brings down the pipeline + If we want to make an exception we can use known issues these days + +2019-03-20 18:36:17 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Be a bit more resilient in GstValidateListener + And handle exception decoding received json info + +2019-03-20 18:26:19 -0300 Thibault Saunier + + * validate/data/scenarios/fast_forward.scenario: + * validate/data/scenarios/seek_forward.scenario: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Implement a way to execute an action on message + And use it for seek forward and fast forward scenarios + +2019-03-19 21:21:09 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Add a way to require a non fatal error when matching a fatal one + +2019-03-19 12:16:13 -0300 Thibault Saunier + + * validate/launcher/utils.py: + validate:launcher: Add a way to force coloration + +2019-03-19 12:15:35 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/utils.py: + validate:launcher: factor out TTY check and enhance iteration output + +2019-03-19 10:22:26 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Clear up last seek on EOS generating a 'stop' action + Otherwise there is a race leading to a segfault where ASYNC_DONE is + received *after* generating EOS ourselves: + ``` + Executing stop ( + - generated-after-eos=true + ) + + **Stack trace**: + ... + Thread 1 (Thread 0x7f3c3e50df00 (LWP 10183)): + #8 0x00007f3c3f01bea5 in g_cclosure_marshal_generic (closure=, return_gvalue=, n_param_values=, param_values=, invocation_hint=, marshal_data=) at gclosure.c:1496 + #9 0x00007f3c3f01b3dd in g_closure_invoke (closure=0x1855980, return_value=0x0, n_param_values=2, param_values=0x7ffda2e0a7e0, invocation_hint=0x7ffda2e0a760) at gclosure.c:810 + #10 0x00007f3c3f02e983 in signal_emit_unlocked_R (node=node@entry=0x15af670, detail=detail@entry=298, instance=instance@entry=0x171fc80, emission_return=emission_return@entry=0x0, instance_and_params=instance_and_params@entry=0x7ffda2e0a7e0) at gsignal.c:3635 + #11 0x00007f3c3f037aaa in g_signal_emit_valist (instance=, signal_id=, detail=, var_args=var_args@entry=0x7ffda2e0a9c0) at gsignal.c:3391 + [Inferior 1 (process 10183) detached] + ``` + +2019-03-19 10:12:42 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Add generated known issue list to the logs instead of printing then + +2019-03-19 09:43:55 -0300 Thibault Saunier + + * validate/launcher/utils.py: + validate:launcher: Add a way to force stdout coloring + +2019-03-18 16:52:11 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + * validate/tests/launcher_tests/test_validate.py: + validate:launcher: Rework expected-issues data format + Instead of having the issues centered on the test classes, they + are now focusing on the "bug". + And harmise names on `expected_issue` not `expected_failures` + +2019-03-18 14:30:59 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Allow printing log URL on a CI server + +2019-03-18 11:09:10 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Fix getting skip-parser when generating media descriptor + On old version it didn't exist + +2019-03-18 10:32:06 -0300 Thibault Saunier + + * validate/launcher/main.py: + validate:launcher: Review default for `--mute` + `--mute` is now True by default and added `--unmute` if the user wants + to have visual/audio feedback. It was really annoying to have thousand + of window pop up by default + +2019-03-17 14:39:38 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Allow referencing known issue by exiting signal names + +2019-03-16 22:52:53 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/launcher/baseclasses.py: + validate:launcher: Output markdown as much as possible + +2019-03-16 21:37:16 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + * validate/launcher/utils.py: + validate:launcher: Implement bug checks for gitlab + And use new gitlab urls for all the bugs + +2019-03-16 21:37:37 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/httpserver.py: + * validate/launcher/vfb_server.py: + validate:launcher: Enhance and standardize output + +2019-03-16 18:05:56 -0300 Thibault Saunier + + * validate/data/scenarios/change_state_intensive.scenario: + * validate/data/scenarios/fast_backward.scenario: + * validate/data/scenarios/fast_forward.scenario: + * validate/data/scenarios/seek_backward.scenario: + * validate/data/scenarios/seek_forward.scenario: + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Add a way to ignore EOS + And ignore then in seek_forward/backward to avoid cases where the pipeline EOS + before we have the chance to launch the following seek, see: + https://ci.gstreamer.net/job/GStreamer-master-meson-validate/3483/testReport/junit/(root)/gst-validate-launcher/validate_rtsp_playback_seek_backward_raw_h264_1_mp4/ + +2019-03-16 16:28:15 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Take into account the timeout when checking EOS + +2019-03-16 12:21:34 -0300 Thibault Saunier + + * debug-viewer/GstDebugViewer/Common/generictreemodel.py: + * debug-viewer/GstDebugViewer/GUI/app.py: + * debug-viewer/GstDebugViewer/GUI/models.py: + * debug-viewer/gst-debug-viewer: + * hooks/pre-commit-python.hook: + * tracer/gsttr-stats.py: + * tracer/gsttr-tsplot.py: + * tracer/tracer/analysis_runner.py: + * tracer/tracer/parser.py: + * tracer/tracer/structure.py: + * tracer/tracer/structure_perf.py: + * validate/docs/launcher/conf.py: + * validate/launcher/RangeHTTPServer.py: + * validate/launcher/baseclasses.py: + * validate/launcher/httpserver.py: + * validate/launcher/loggable.py: + * validate/tools/gst-validate-analyze: + * validate/tools/gst-validate-launcher.in: + Pass python files through autopep8 + +2019-03-15 23:46:00 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Help the user add known issues + And make it clear a bug should be opened about it + +2019-03-15 07:39:04 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Enhance dumping log files output + +2019-03-13 19:08:25 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/config.py.in: + * validate/launcher/main.py: + * validate/launcher/meson.build: + * validate/launcher/utils.py: + validate:launcher: Use gst-integration-testsuites subproject as default testsuite repo if avalaible + +2019-03-08 15:44:31 +0100 Philipp Zabel + + * validate/gst/validate/gst-validate-scenario.c: + validate: fix pause duration handling + Commit 394242c2248a ("validate:scenario: Enhance variable + implementation") caused the duration parameter to be stored + as a double instead of GstClockTime, which the _execute_pause + implementation expects. Fix the parameter type and use + gst_validate_action_get_clocktime to handle duration correctly. + https://gitlab.freedesktop.org/gstreamer/gst-devtools/merge_requests/73 + +2019-04-01 15:23:21 -0400 Nicolas Dufresne + + * debug-viewer/meson.build: + debug-viewer: Use python.install_sources() + With current implementation we would try and install into the system + path regardless of the prefix. On top of that, we could install any left + over pyc file and would install the unit test also. + To fix this, we now list every files to be installed and use + python.install_sources(), leaving to meson the decision on where things + should be installed. + +2019-04-01 14:48:54 -0400 Nicolas Dufresne + + * meson.build: + * validate/gst/validate/meson.build: + meson: validate: Fix linking error missing GstBaseTransform + There is a mockdecryptor that has been added into validate-sources and + this element is base on GstBaseTransform. This added a deps against + gstbase which was leading to linking errors when building with meson. + +2019-04-01 21:49:19 +0900 Jimmy Ohn + + * codecanalyzer/src/codecanalyzer.c: + * codecanalyzer/src/gst_analyzer.c: + * codecanalyzer/src/plugins/gst/analyzersink/gstanalyzersink.c: + codecanalyzer: Use glib variant of strcmp + safer, and avoids missing include + +2019-02-15 14:59:20 +0900 Jimmy Ohn + + * validate/gst/validate/validate.c: + validate: Print some log when environment variable is not set + print some log when environment variable is not set + +2019-03-23 19:48:29 +0000 Tim-Philipp Müller + + * validate/gst/validate/meson.build: + meson: validate: actually pass extra arguments to gnome.generate_gir() + Especially the init section and the --quiet. + Remove the whole manual build/source dir include addition + to the g-ir-scanner args seeing that things worked fine + without the args being passed to the scanner at all. + +2019-03-23 19:47:24 +0000 Tim-Philipp Müller + + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/gst-validate-override-registry.h: + * validate/gst/validate/gst-validate-utils.c: + validate: fix g-i warnings + gst-validate-utils.c:914: gst_validate_element_matches_target: unknown parameter 'structure' in documentation comment, should be 's' + gst-validate-override-registry.h:49: gst_validate_override_registry_get_override_list: return value: Missing (element-type) annotation + +2019-03-23 19:22:29 +0000 Tim-Philipp Müller + + * meson.build: + g-i: pass --quiet to g-ir-scanner + This suppresses the annoying 'g-ir-scanner: link: cc ..' output + that we get even if everything works just fine. + We still get g-ir-scanner warnings and compiler warnings if + we pass this option. + +2019-03-22 15:37:37 -0400 Xavier Claessens + + * validate/gst/validate/gst-validate-mockdecryptor.c: + Avoid C99 declaration in 'for' loop + +2019-03-22 10:48:03 -0300 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + validate:launcher: Set CK_MULTIPLIER=10 in GstCheck tests when using valgrind + Fixes https://gitlab.freedesktop.org/gstreamer/gst-devtools/issues/39 + +2019-03-21 13:06:00 +0000 Tim-Philipp Müller + + * debug-viewer/meson.build: + * meson.build: + meson: use new 'python' module instead of deprecated 'python3' one + https://github.com/mesonbuild/meson/pull/4169 + +2019-03-14 23:17:16 +1100 Matthew Waters + + * validate/gst/overrides/meson.build: + validate: allow building a static overrides library + +2019-02-21 17:22:10 +0000 Charlie Turner + + * validate/gst/validate/Makefile.am: + * validate/gst/validate/gst-validate-mockdecryptor.c: + * validate/gst/validate/gst-validate-mockdecryptor.h: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/meson.build: + validate: Add a mock decryptor element. + +2019-03-15 17:38:15 +0000 Alicia Boya García + + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/gst-validate-override-registry.h: + * validate/gst/validate/gst-validate-runner.c: + * validate/plugins/flow/gstvalidateflow.c: + validateflow: Fail when a pad is not attached + Previously validateflow tests did not fail when the pad was not + attached. + This was a limitation caused by how the Validate API worked. Before, the + `notify::validate-runner` signal was not emitted until a monitor was + attached to the override. This made impossible to listen for the + runner's `stopping` signal. + This patch fixes the problem by setting `validate-runner` for all + existing overrides when the runner is initialized and adding checks in + validateflow to error in the case no pad was attached. + +2019-03-10 17:07:08 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/utils.py: + validate:launcher: Move '_format_config_template' to the utilities + So it can be reused by GES tests + +2019-03-10 17:06:13 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + validate: Check that position <= duration from the pipeline monitor + We should not require a scenario for that check to happen + +2019-03-10 17:05:15 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Cleanup spurious error message + +2019-03-10 17:03:09 -0300 Thibault Saunier + + * docs/plugins/validateflow.md: + * validate/plugins/flow/formatting.c: + * validate/plugins/flow/formatting.h: + * validate/plugins/flow/gstvalidateflow.c: + validate-flow: Add an ignored-event-fields configuration + It replaces `record-stream-id` as it is a more generic way of doing + the same thing. + +2019-02-07 15:50:26 -0300 Thibault Saunier + + * docs/plugins/validateflow.md: + docs: Document the validateflow plugin + +2019-03-06 10:15:21 +0100 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + validate: More usage of g_strcmp0 instead of strcmp + +2019-03-06 10:11:02 +0100 Edward Hervey + + * validate/plugins/flow/gstvalidateflow.c: + validateflow: Use glib variant of strcmp + safer, and avoids missing include + +2019-03-04 15:20:49 +0100 Edward Hervey + + * validate/plugins/flow/formatting.c: + * validate/plugins/flow/gstvalidateflow.c: + validate: Include config.h before anything else + To avoid double-defines (such as GST_LEVEL_DEFAULT) + +2019-03-04 09:15:11 +0000 Tim-Philipp Müller + + * meson.build: + * validate/NEWS: + * validate/RELEASE: + * validate/configure.ac: + * validate/meson.build: + Back to development + +=== release 1.15.2 === + +2019-02-26 12:00:40 +0000 Tim-Philipp Müller + + * meson.build: + * validate/ChangeLog: + * validate/NEWS: + * validate/RELEASE: + * validate/configure.ac: + * validate/gst-validate.doap: + * validate/meson.build: + Release 1.15.2 + +2019-02-26 19:42:08 +0000 Tim-Philipp Müller + + * validate/data/scenarios/Makefile.am: + scenarios: don't clobber scenarios in 'make install' + /usr/bin/install: will not overwrite just-created '_inst/share/gstreamer-1.0/validate/scenarios/default-seek-flags.scenario' with '../../../../data/scenarios/rtsp_overrides/includes/default-seek-flags.scenario' + https://gitlab.freedesktop.org/gstreamer/gst-devtools/merge_requests/44#note_123683 + +2019-02-26 18:53:28 +0000 Tim-Philipp Müller + + * validate/win32/common/libgstvalidate.def: + validate: add new API to exports file + +2019-02-01 10:02:22 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + validate: Notice that issue summaries should be 'stable' + As they are used in the testsuite to define known issues + +2019-02-17 15:38:53 +0100 Alicia Boya García + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + gst-validate-scenario: Make waits optional in appsrc-push + While in many cases it's desirable to wait for a buffer to be pushed + downstream when using appsrc-push, in some cases this is not possible as + such pushing action is dependent on following actions that would not be + executed if we wait. + An example for this is prerolling: + appsrc ! qtdemux ! video/x-h264 ! decodebin name=dec ! %(videosink)s + 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 + In order for the preroll to occur, both the appsrc needs to push the + buffer and the state needs to reach PLAYING. But `set-state` cannot + finish if the buffer has not been pushed (the state transition does not + finish) and conversely pushing the buffer will not finish until the + state has reached. + Making appsrc-push not wait for the buffer solves this problem. This + patch makes appsrc-push aware of this issue by only waiting for the + buffer to be pushed if the pipeline is in a state that allows buffers to + flow. + +2019-02-21 22:01:24 +0100 Alicia Boya García + + * validate/gst/validate/gst-validate-scenario.c: + gst-validate-scenario: Fix (another) race condition in EOS handling + Since gst_validate_action_set_done() is asynchronous, the bus EOS + handler may already be running before the action is actually finished. + This patch ensures that is not a problem. + +2019-02-19 17:36:23 +0000 Charlie Turner + + * validate/launcher/apps/gstvalidate.py: + validateflow: interpolate folder directories into pipeline descriptions. + Allow 'pipeline' fields in validateflow to be interpolated with + directory keys like $(medias)s. + +2019-02-07 15:36:41 +0100 Guillaume Desmottes + + * validate/docs/validate/envvariables.xml: + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.h: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + validate: allow config to check for minimum buffer frequency on pads + This change allow tests to check performance of elements by checking the + frequency at which buffers are pushed on src pads. + I re-used most of the logic from fpsdisplaysink to compute the + frequency. + We can now uses something like: + GST_VALIDATE_CONFIG='core,min-buffer-frequency=60,target-element-factory-name=v4l2src' + The 'buffer-frequency-start' optional field can be used to ignore the + frequency during the start of the pipeline. This is useful when testing live + pipelines where configuring and setting up elements can take some time slowing + down the first buffers. + +2019-02-11 16:07:28 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/gst-validate-utils.h: + validate: factor out gst_validate_element_matches_target() + +2019-02-18 11:05:26 +0000 Charlie Turner + + * validate/plugins/flow/gstvalidateflow.c: + validateflow: Fix double-free on stdout + +2019-02-04 13:19:26 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/tests/check/validate/expression_parser.c: + validate: report: Fix the way we print 'repeat' values + +2019-02-04 13:18:04 -0300 Thibault Saunier + + * validate/gst/validate/validate.c: + * validate/tests/check/meson.build: + * validate/tests/check/validate/expression_parser.c: + validate: Force LC_NUMERIC to C as it is required by our expression parser + And... add some expression parser unit tests + +2019-02-03 20:05:36 -0300 Thibault Saunier + + * validate/data/scenarios/alternate_fast_backward_forward.scenario: + * validate/data/scenarios/fast_backward.scenario: + * validate/data/scenarios/fast_forward.scenario: + * validate/data/scenarios/includes/default-seek-flags.scenario: + * validate/data/scenarios/reverse_playback.scenario: + * validate/data/scenarios/rtsp_overrides/includes/default-seek-flags.scenario: + * validate/data/scenarios/scrub_backward_seeking.scenario: + * validate/data/scenarios/scrub_backward_seeking_full.scenario: + * validate/data/scenarios/scrub_forward_seeking.scenario: + * validate/data/scenarios/scrub_forward_seeking_full.scenario: + * validate/data/scenarios/seek_backward.scenario: + * validate/data/scenarios/seek_forward.scenario: + * validate/data/scenarios/seek_forward_backward.scenario: + * validate/data/scenarios/seek_with_stop.scenario: + * validate/data/scenarios/simple_seeks.scenario: + * validate/data/scenarios/update_start.scenario: + * validate/data/scenarios/update_stop.scenario: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/tests/check/meson.build: + * validate/tests/check/validate/scenario.c: + validate:scenario: Enhance variable implementation + - Stop arbitrarily consider params as ClockTime based on their names + but add a convetion that the `.type` field of the ActionType should + end by `(GstClockTime)` when it is a clock time. + +2019-02-03 20:03:40 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario: Make gst_validate_action_new public + Mainly so it can be used in unit tests. + +2019-02-02 13:43:35 -0300 Thibault Saunier + + * validate/data/scenarios/fast_forward.scenario: + * validate/data/scenarios/reverse_playback.scenario: + * validate/data/scenarios/scrub_backward_seeking.scenario: + * validate/data/scenarios/scrub_backward_seeking_full.scenario: + * validate/data/scenarios/scrub_forward_seeking.scenario: + * validate/data/scenarios/scrub_forward_seeking_full.scenario: + * validate/data/scenarios/seek_backward.scenario: + * validate/data/scenarios/seek_forward.scenario: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + validate:scenario: Allow using set-vars from numeric expressions + And require them to follow the `$varname` (can't be $(varname) as + parenthesis have another meaning in those expressions). + Still accept "duration" and "position" as varname for backward compat + but update our scenarios anyway. + +2019-02-02 13:32:50 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: scenario: Parse playback times as we execute the scenario + This way we will be able to use 'set-vars' for it + +2019-02-01 22:54:13 -0300 Thibault Saunier + + * validate/data/scenarios/alternate_fast_backward_forward.scenario: + * validate/data/scenarios/fast_backward.scenario: + * validate/data/scenarios/fast_forward.scenario: + * validate/data/scenarios/includes/default-seek-flags.scenario: + * validate/data/scenarios/reverse_playback.scenario: + * validate/data/scenarios/rtsp_overrides/includes/default-seek-flags.scenario: + * validate/data/scenarios/scrub_backward_seeking.scenario: + * validate/data/scenarios/scrub_backward_seeking_full.scenario: + * validate/data/scenarios/scrub_forward_seeking.scenario: + * validate/data/scenarios/scrub_forward_seeking_full.scenario: + * validate/data/scenarios/seek_backward.scenario: + * validate/data/scenarios/seek_forward.scenario: + * validate/data/scenarios/seek_forward_backward.scenario: + * validate/data/scenarios/seek_with_stop.scenario: + * validate/data/scenarios/simple_seeks.scenario: + * validate/data/scenarios/update_start.scenario: + * validate/data/scenarios/update_stop.scenario: + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Enforce a synthax $(varname) to reference variables + This way it is clear that you are using a variable reading the scenario + and we can verify that what the scenario writer intents is to use an + already set variable. + +2019-02-07 17:34:56 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-scenario.c: + validate: don't override max-latency if config contains multiple structs + gst_validate_utils_get_clocktime() is resetting the value if it's not + present in the struct so we were overriding it on the next iterations. + +2019-02-08 11:46:58 +0100 Guillaume Desmottes + + * validate/launcher/baseclasses.py: + validate: baseclasses: include env variable in logged command + We were missing the env variables in the command written to the log + file, making it impossible to re-run the test later from the logs. + +2019-02-09 01:16:31 +0100 Alicia Boya García + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + gst-validate-scenario: Add GST_VALIDATE_SCENARIO_EOS_HANDLING_LOCK + There was a race in appsrc-push when the pushed buffer caused an EOS. + The EOS event could be handled by the main thread, finishing the test + while the action, executing in the streaming thread, has not finished + yet. + A mutex is now introduced to add mutual exclusion for the two threads so + that an EOS does not cause the termination of the test while the action + is still going. + +2019-02-07 15:42:06 -0300 Thibault Saunier + + * validate/plugins/flow/gstvalidateflow.c: + validateflow: Print some indication that the flow checking is happening + +2019-02-11 11:09:21 +0100 Edward Hervey + + * validate/configure.ac: + * validate/plugins/Makefile.am: + * validate/plugins/flow/Makefile.am: + validate: Add autotools support for flow plugin + +2019-02-10 01:23:50 -0300 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + Revert "validate:launcher: Cache the result of meson introspect" + This reverts commit 05ce6d3b92e88341bd9743b021b33ce606c5d1bc. + We can't do that as it breaks meson logic to set envvars + +2019-02-09 17:25:03 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Do not sort tests all the time + Do it once only once it is fully populated + +2019-02-09 17:24:10 -0300 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + validate:launcher: Cache the result of meson introspect + Running it takes quite some time and we can easily cache it. + +2019-02-09 17:23:28 -0300 Thibault Saunier + + * validate/tools/gst-validate-launcher.in: + validate:launcher: Add a simple way to profile app + +2019-02-08 14:36:56 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-scenario.c: + validate: scenario: not need to use an atomic to handle dropped count + It's all handled from the same thread. + +2019-02-08 14:23:15 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-scenario.c: + validate: scenario: fix dropped checking when terminating scenario + We want to early return if either no max value has been set for the + scenario or if we didn't receive any QoS information. + +2019-02-08 13:32:12 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-scenario.c: + validate: properly namespace config related checks + +2018-10-28 17:27:22 +0000 Alicia Boya García + + * validate/gst/validate/gst-validate-override.h: + * validate/gst/validate/gst-validate-scenario.c: + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + * validate/plugins/flow/formatting.c: + * validate/plugins/flow/formatting.h: + * validate/plugins/flow/gstvalidateflow.c: + * validate/plugins/flow/meson.build: + * validate/plugins/meson.build: + New validate plugin: validateflow + validateflow can be used to check the buffers and events flowing through + a custom pipeline match an expectation file. This can be used to test + non-regular-playback use cases like demuxers handling adaptive streaming + fragment pushing. + This patch includes also new actions used for these cases: + `appsrc-push`, `appsrc-eos` and `flush` (plus `checkpoint`, which is + only available with validateflow). + +2019-02-07 17:04:52 +0100 Guillaume Desmottes + + * validate/launcher/baseclasses.py: + baseclass: add_validate_config: don't use self.proc_env + self.proc_env is created when starting the test but this API can be call + by generator when creating the test. + +2019-02-06 18:24:19 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-scenario.c: + validate: allow scenarios to define a max nb of dropped buffers + The 'max-dropped' description field can now be used to specify the max + number of buffers than can be dropped by the QoS system. + +2019-02-05 23:46:40 -0300 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + validate:launcher: Error out in the check testsuite if rebuilding failed + +2019-02-04 17:03:01 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-scenario.c: + validate: allow scenarios to define max pipeline latency + The 'max-latency' description field can now be used to specify the max + latency allowed for the running pipeline. + +2019-02-02 01:23:16 +0100 Mathieu Duponchelle + + * validate/data/scenarios/Makefile.am: + * validate/data/scenarios/alternate_fast_backward_forward.scenario: + * validate/data/scenarios/fast_backward.scenario: + * validate/data/scenarios/fast_forward.scenario: + * validate/data/scenarios/includes/default-seek-flags.scenario: + * validate/data/scenarios/meson.build: + * validate/data/scenarios/reverse_playback.scenario: + * validate/data/scenarios/rtsp_overrides/includes/default-seek-flags.scenario: + * validate/data/scenarios/scrub_backward_seeking.scenario: + * validate/data/scenarios/scrub_backward_seeking_full.scenario: + * validate/data/scenarios/scrub_forward_seeking.scenario: + * validate/data/scenarios/scrub_forward_seeking_full.scenario: + * validate/data/scenarios/seek_backward.scenario: + * validate/data/scenarios/seek_forward.scenario: + * validate/data/scenarios/seek_forward_backward.scenario: + * validate/data/scenarios/seek_with_stop.scenario: + * validate/data/scenarios/simple_seeks.scenario: + * validate/data/scenarios/update_start.scenario: + * validate/data/scenarios/update_stop.scenario: + * validate/launcher/apps/gstvalidate.py: + Scenarios: override seek flags for RTSP tests + Our RTSP server is not accurate, it makes no sense to perform + accuracy checks on the client-side segments. + +2019-02-01 20:01:40 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Set variables on all action fields + +2019-02-01 19:30:44 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-reporter.c: + validate: Handle G_LOG_ERROR in our glog handler + +2019-02-01 19:03:04 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Rename action 'define-consts' to 'set-vars' + Those are not consts are they can be modified at runtime + +2019-02-01 18:41:07 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + validate: Cleanup flags/enum_from_string + +2019-02-01 23:08:16 +0100 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-scenario.c: + scenario: prioritize SCENARIOS_PATH when including + +2019-02-01 01:24:19 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate: Let people know the testsuite starts running + Now that we do not print infos about successful tests when redirecting. + +2019-02-01 00:08:45 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-reporter.c: + validate: Keep trying to print stack traces when our log hanlder is removed + There are cases where a crash happens after the program ends + +2019-01-31 23:43:28 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Give pipeline position when failling on EOS + +2019-01-31 23:35:50 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Do not print passing tests if not running in a tty + +2019-01-31 22:37:17 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Explicitely mark generated stop() action as such + +2019-01-30 15:57:13 -0300 Thibault Saunier + + * validate/launcher/apps/pyunittest.py: + validate:launcher:punittest: Raise an exception if a testsuite can't be loaded + +2015-11-25 16:10:50 +0100 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.h: + pad-monitor: Remove unused pad getrange override + +2015-11-25 14:20:31 +0100 Edward Hervey + + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/validate.c: + validate: Use g_object_{get|set}_qdata where applicable + This provides a substantial speedup compared to using strings + +2019-01-30 01:24:16 +0100 Mathieu Duponchelle + + * validate/launcher/baseclasses.py: + TestsManager: stop displaying blacklisted tests on stdout + It's basically spam, better suited to the debug logs + +2019-01-29 15:59:44 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Never print lines larger than the terminal + +2019-01-29 12:54:01 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate: scenario: Mark action as being executed right before calling ->execute + And make sure that we do not try to execute the following action + In the case the action type leads to the GMainContext to be ieterated + +2018-12-11 11:42:25 +0200 Jordan Petridis + + * validate/launcher/reporters.py: + validate: Report the full test name in the xunit file + Looks like gitlab prefers this way of representing tests as it + displays only the name field in its junit reports. + Close #32 + +2019-01-26 10:27:47 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Make baseclasses.py pep8 compliant + +2019-01-25 22:27:07 -0300 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + validate:launcher: Handle launching launching a sub launcher + If you use validate-launcher in a meson testsuite, those test now + gets integrated as one unique testsuite (with a pretty long namespace). + +2019-01-25 22:13:28 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + validate:launcher: Refactor the "main" function + - Move the parser code into a `LauncherConfig.create_parser()` method + - Remove the need to pass libsdir to the _TestsLauncher object + - Extract out a `setup_launcher_from_args` function + +2019-01-25 22:09:30 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + launcher: Move http serveur and xvfb server to the main test runner object + No good reason for it to be in the main function + +2019-01-25 22:06:14 -0300 Thibault Saunier + + * validate/launcher/apps/pyunittest.py: + launcher: Cleanup the way we find python test command line + By setting it before the test base class adds the current testsuite name in the classname + +2019-01-25 22:03:57 -0300 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + launcher: Fix test listing in meson VS gstcheck tests + You might select tests that match Meson but not gstcheck in which + case the 'meson only' variant is exposed but those should never exist. + +2019-01-26 09:19:35 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: Add `%(config_path)s` in the pipeline desc vars + When defining pipelines_descriptions to run test on in a `.json` file, you might + need to point to paths in the testsuite directory (for media files URIs + for example), you can now do + `"pipeline": "filesrc location="$(config_path)s/../medias/some/file.mkv...` + +=== release 1.15.1 === + +2019-01-17 10:01:50 +0000 Tim-Philipp Müller + + * meson.build: + * validate/ChangeLog: + * validate/NEWS: + * validate/RELEASE: + * validate/configure.ac: + * validate/gst-validate.doap: + Release 1.15.1 + +2019-01-17 09:58:47 +0000 Tim-Philipp Müller + + * validate/meson.build: + Fix distcheck + Work around broken disthook check in release.mak so we don't + have to update the common submodules for that (applies only + to this module because the version number is in the top-level + meson.build but the package/dist directory is a subdir). This + only became a problem now because the common submodule hadn't + been updated for the last few years. + +2019-01-17 09:38:13 +0000 Tim-Philipp Müller + + * validate/win32/common/libgstvalidate.def: + win32: update .def file for new API + Fixes distcheck + +2019-01-15 16:52:24 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Display unix nickname of signals leading to test failure + +2019-01-15 16:05:41 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Fix error message about 'crashed' test + +2019-01-14 22:55:35 +0100 Alicia Boya García + + * hooks/pre-commit-python.hook: + pre-commit-python: Allow line breaks between binary operators + pre-commit-python overrides the list of ignored Python style errors. + Unfortunately, before this patch the list did not exclude W503 and + W504 (which are otherwise ignored by default). + The consequence of having those two warnings enabled at the same time is + that it's not possible to break lines on binary operators, which is an + unreasonable unintentional restriction: + 'validateflow': "validateflow, expectations-dir=\"" + + expectations_dir + "\", actual-results-dir=\"" + + actual_results_dir + "\"", + W504 line break after binary operator + 'validateflow': "validateflow, expectations-dir=\"" + + expectations_dir + "\", actual-results-dir=\"" + + actual_results_dir + "\"", + W503 line break before binary operator + This patch excludes W503 so that there is a valid style for breaking + lines on binary operators. + +2019-01-12 15:25:53 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-runner.c: + validate: Plug newly introduced leak + +2019-01-12 09:59:12 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-runner.c: + validate: Use filename instead of full path in dotfiles names + +2019-01-07 00:06:30 +0100 Thibault Saunier + + * validate/tests/check/validate/padmonitor.c: + validate:tests: Fix race in `validate_padmonitor.buffer_outside_segment` + We were using a fakesrc which data flow was potentially breaking the + test. + +2019-01-07 00:05:50 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + validate: Lower a ERROR message to INFO + This behaviour is totally valid when running unit tests + +2019-01-03 14:15:16 +0100 Thibault Saunier + + * validate/tests/check/validate/padmonitor.c: + validate:tests: s/discount_buffer/discont_buffer/ + +2019-01-03 12:09:09 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-monitor.c: + * validate/tests/check/validate/padmonitor.c: + validate:tests: Fix race in `validate_padmonitor.buffer_before_segment` + We were using a fakesrc which data flow was potentially breaking the + test. Stop using it and remove dead code. + Fixes #34 + +2018-12-17 10:34:43 +0100 Guillaume Desmottes + + * validate/launcher/baseclasses.py: + validate: fix crash if timeout when media_descriptor is None + Some tests may not have any media_descriptor. If those were failing to + shutdown after EOS we were calling get_protocol() on None. + +2018-12-14 12:00:18 +0100 Alexandru Băluț + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: Fix error message + +2018-12-14 11:17:41 +0100 Alexandru Băluț + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: Fix discovery of commands + +2018-12-10 13:25:58 +1100 Matthew Waters + + * meson.build: + * validate/gst/validate/meson.build: + * validate/plugins/extra_checks/meson.build: + gst: allow building static libraries for e.g. Android/iOS + +2018-12-09 17:55:37 +0530 Nirbheek Chauhan + + * validate/win32/common/libgstvalidate.def: + meson: Fix build on {cross-,}win{32,64} + We use visual studio module definitions for the list of symbols to + export when targetting Windows. Fixes CI failure: + ../validate/tools/gst-validate.c:460: undefined reference to `gst_validate_spin_on_fault_signals' + +2018-12-07 09:05:09 -0300 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + validate:launcher: Do not CK_FORK on our test + Otherwise the process can't cleanly quit on assertion because of the way libcheck runner is implemented + +2018-12-07 09:03:24 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/gst-validate-utils.h: + * validate/launcher/baseclasses.py: + * validate/tools/gst-validate-media-check.c: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: launcher: Add a way to retrieve trace without coredumpctl + Simply spnning on segfaults (like gst-launch) and catch that in + the launcher to transform the timeout into a segfault and grab a gdb + backtrace + +2018-12-06 23:17:29 -0300 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + validate:launcher: Do not take CK_DEFAULT_TIMEOUT into account to set the timeout + The timeout is what is set in the meson build definition otherwise we will not behave as 'meson test' which we should avoid + +2018-12-06 15:35:18 -0300 Thibault Saunier + + * validate/launcher/reporters.py: + validate:launcher: Make failure as in our xunit reporter + I think it was a mistake to call them as the two notions are + different (we marked failed test as "failures" in the node). + Should make gitlab happy with our file! + +2018-12-06 11:53:10 +0200 Sebastian Dröge + + * validate/common: + Automatic update of common submodule + From eb6a86e to 59cb678 + +2018-11-30 10:59:51 -0300 Thibault Saunier + + * validate/launcher/main.py: + * validate/launcher/utils.py: + validate:launcher: Add a GST_VALIDATE_LAUNCHER_MAIN_DIR env variable + This is generally usefull so we do not have to pass -M every time we launch the launcher + And it adds support for nesting launcher calls always respecting the provided main directory + + Fix some new pep8 errors + +2018-11-28 10:14:35 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: Don't about unexisting tests when filtering tests + It was wrong + +2018-11-28 10:11:00 -0300 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + validate:launcher: Fix setting meson tests as "parallel" + +2018-11-26 10:13:22 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: Avoid using not yet set variables + And make the file pep8 compliant with latest pep8 checker. + +2018-11-25 11:36:06 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate: launcher: Add a way to load pipeline tests from a scenario + +2018-11-22 21:06:36 -0300 Thibault Saunier + + * validate/launcher/main.py: + validate: Update default testsuite git repository + +2018-11-17 09:48:41 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + validate: Enhance printing actions with fields + If you have maby field, printed actions where unreadable, clean that + up by adding new lines. + +2018-11-17 09:09:34 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Add an action to 'include' another scenario + This is particularly useful for scenario that define constants + that are used to check video frame checksum for example, we can + now have one single 'scenario' file that defines consts for the + checksum of the frames, and those can be reused everywhere. + +2018-10-28 15:21:38 +0000 Thibault Saunier + + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/gst-validate-override.c: + * validate/gst/validate/gst-validate-override.h: + validate:override: Notify override object when they get attached + +2018-10-29 15:37:11 +0000 Alicia Boya García + + * validate/gst/validate/gst-validate-monitor.c: + validate: fix bug monitor subscriptions on pads by name + gst_validate_override_register_by_name() was not working when using a + pad name because by the time gst_validate_pad_monitor_do_setup() + was called to set the name of the monitor it was too late for overrides + to have any effect. + Patch written by Thibault. + +2018-11-12 13:14:42 +0200 Jordan Petridis + + * .gitlab-ci.yml: + Add Gitlab CI configuration + This commit adds a .gitlab-ci.yml file, which uses a feature + to fetch the config from a centralized repository. The intent is + to have all the gstreamer modules use the same configuration. + The configuration is currently hosted at the gst-ci repository + under the gitlab/ci_template.yml path. + Part of https://gitlab.freedesktop.org/gstreamer/gstreamer-project/issues/29 + +2018-11-05 05:54:43 +0000 Matthew Waters + + * .gitmodules: + * validate/gst-validate.doap: + Update git locations to gitlab + +2018-10-28 14:54:47 +0000 Thibault Saunier + + * validate/gst/validate/gst-validate-override-registry.c: + validate: Allow connecting reporters by name for overrides + Using the element that owns the pad on which we are connecting + was not making sense. + +2018-10-28 11:03:54 +0000 Philippe Normand + + * debug-viewer/gst-debug-viewer: + debug-viewer: Python3 port follow-up + One print statement wasn't ported to Python3. + +2018-10-27 09:01:53 -0400 Xavier Claessens + + * debug-viewer/meson.build: + * meson_options.txt: + meson: add option to disable translation + https://bugzilla.gnome.org/show_bug.cgi?id=797342 + +2018-09-19 11:50:09 +0900 Wonchul Lee + + * validate/gst-libs/gst/video/gssim.c: + * validate/gst-libs/gst/video/gstvalidatessim.c: + * validate/gst-libs/gst/video/gstvalidatessim.h: + * validate/gst/validate/gst-validate-override.c: + * validate/gst/validate/gst-validate-override.h: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/media-descriptor-parser.c: + * validate/gst/validate/media-descriptor-writer.c: + * validate/gst/validate/media-descriptor.c: + * validate/plugins/ssim/gstvalidatessim.c: + validate: Update for g_type_class_add_private() deprecation in recent GLib + https://gitlab.gnome.org/GNOME/glib/merge_requests/7 + +2018-09-08 19:24:41 -0300 Thibault Saunier + + * validate/gst-libs/gst/video/gssim.c: + * validate/gst-libs/gst/video/gssim.h: + Update for g_type_class_add_private() deprecation in recent GLib + +2018-09-08 11:12:32 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + launcher:scenario: Fix the way we compute scenario path/name when paths supplied + We were just iterating over the list without any check and could end + up with a patch that was not corresponding to the actual scenario. + +2018-09-07 15:59:49 -0300 Thibault Saunier + + * validate/launcher/vfb_server.py: + validate:launcher: Just wait for a while before considering Xvfb is ready if xset is not present + This is what xvfb-run so let's consider it good enough + +2018-08-01 21:05:32 -0400 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + validate:launcher: Use fakevideosink everywhere it makes sense. + +2018-09-06 17:29:24 +0530 Nirbheek Chauhan + + * validate/gst/overrides/gst-validate-default-overrides.c: + validate: Export the plugin symbol correctly + Otherwise it doesn't get correctly exported when building with MSVC + +2018-09-01 12:09:32 +0530 Nirbheek Chauhan + + * meson.build: + * meson_options.txt: + * validate/meson.build: + * validate/tests/check/meson.build: + * validate/tests/meson.build: + meson: Add a feature option for tests + This autodetection is needed on iOS inside Cerbero where + gstreamer-check-1.0 is not available. + +2018-08-31 15:21:05 +0530 Nirbheek Chauhan + + * meson.build: + meson: gst_version_* are ints, convert them early + Fixes error reported by ceyusa: + gst-devtools/meson.build:23:0: ERROR: Multiplication works only with integers. + +2018-08-31 14:52:04 +0530 Nirbheek Chauhan + + * meson.build: + * validate/gst/overrides/meson.build: + * validate/gst/validate/meson.build: + meson: Maintain macOS ABI through dylib versioning + Requires Meson 0.48, but the feature will be ignored on older versions + so it's safe to add it without bumping the requirement. + Documentation: + https://github.com/mesonbuild/meson/blob/master/docs/markdown/Reference-manual.md#shared_library + +2018-07-31 23:29:57 +0530 Nirbheek Chauhan + + * validate/gst/meson.build: + * validate/gst/overrides/meson.build: + meson: Build gstvalidate-default-overrides-1.0 + Needed by Cerbero. + +2018-07-30 21:36:48 +0200 Alicia Boya García + + * validate/launcher/baseclasses.py: + gst-validate-launcher: Print copypaste-friendlier commands + This patch removes the quotes surrounding the command shown by + gst-validate to reproduce the issues -- which were troublesome when + copying and pasting. + It also introduces escaping for the arguments, so that the command line + can be copied and pasted in the terminal without further changes. + https://bugzilla.gnome.org/show_bug.cgi?id=796897 + +2018-07-25 17:27:03 +0530 Nirbheek Chauhan + + * meson.build: + * meson_options.txt: + * validate/docs/validate/meson.build: + * validate/meson.build: + meson: Convert common options to feature options + The rest will be converted later, these are necessary for gst-build to + set options correctly. + https://bugzilla.gnome.org/show_bug.cgi?id=795107 + +2018-07-14 15:55:34 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Add an action type to validate last sample checksum + +2018-07-19 22:00:17 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate: launcher: Print some ERROR log when inspecting scenario fails + +2018-07-19 18:27:32 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Add a way to define constants to be used in actions + Allowing writing simpler to read scenarios. + +2018-07-14 08:27:05 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Add a way to set rank on all features of a plugin + You often want to make sure that elements from a particular plugins + are always/never plugged, `set-rank,name=plugin-name,rank=XXX` allows + you to simply do that. + +2018-07-12 19:13:09 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Minor output string message + +2018-07-10 13:16:36 +0200 Edward Hervey + + * validate/launcher/reporters.py: + validate: Use 'skipped' keyword in xunit xml + It was always meant to be 'skipped' to be 100% compatible with xunit + xsl. + Makes jenkins happy again + +2018-07-08 17:02:59 -0400 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + validate:launcher: Stop spamming envvars in unit tests command printing. + We used to print the whole environment, making it ugly and hard to + read. + +2018-07-01 11:32:10 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/utils.py: + validate:launcher: Allow retrieving coredumps from within flatpak + +2018-06-19 07:12:20 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: Reset stream-related variables when deactivating + Any local variable related to the stream should be resetted + when the pad is deactivated + Avoids weird issues when elements are re-used (and pads are deactivated + and reactivated). + +2018-06-17 08:34:09 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Fix the --forever switch + It was not stopping on error. + https://bugzilla.gnome.org/show_bug.cgi?id=796608 + +2018-06-15 17:52:47 -0400 Thibault Saunier + + * validate/win32/common/libgstvalidate.def: + validate: Update .def + +2018-06-15 16:42:32 -0400 Thibault Saunier + + * validate/launcher/apps/Makefile.am: + * validate/launcher/apps/meson.build: + * validate/launcher/apps/pyunittest.py: + * validate/launcher/testsuites/Makefile.am: + * validate/launcher/testsuites/meson.build: + * validate/launcher/testsuites/pyunittest.py: + validate:launcher: Add a TestManager to run python tests + Add a stupid simple testsuite made to be configured from the outside + +2018-06-15 15:01:32 -0400 Thibault Saunier + + * validate/gst/validate/media-descriptor.c: + validate: Fix mixup in variable check + +2018-06-15 10:25:33 -0400 Thibault Saunier + + * meson_options.txt: + * validate/meson.build: + meson: Rename the gtkdoc option to gtk_doc + This is what other modules use + +2018-05-24 14:41:27 +0200 Thibault Saunier + + * validate/tools/gst-validate-media-check.c: + validate: media-check: Avoid spamming the MediaInfo file on stdout + +2018-05-23 17:57:23 +0200 Thibault Saunier + + * validate/docs/validate/gst-validate-launcher.xml: + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + * validate/launcher/utils.py: + validate: launcher: Add support for running tests with a pushfile source + Introducing the `.media_info.push` media info extension, which is meant + to let the launcher know that those file should run with the "pushfile://" + protocol. + And allow symlinking "normal" `.media_info` to their `.pushfile` variant + so that both can share the exact same content. + +2018-05-25 15:35:10 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-enum-types.h.template: + * validate/gst/validate/media-descriptor-parser.c: + * validate/gst/validate/media-descriptor-writer.c: + * validate/gst/validate/media-descriptor-writer.h: + * validate/gst/validate/media-descriptor.c: + * validate/gst/validate/media-descriptor.h: + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + * validate/tools/gst-validate-media-check.c: + validate: media-check: Add a way to skip pluggin parsers + This is useful when you want to check only the demuxer output. + - Keep the information in the media file so that we can launch media-check + with the proper arguments in the launcher. Update it accordingly. + - Refactor compare_streams to simplify it, which in the end leads to + reporting all the issues instead of exiting on the first one. + +2018-05-23 01:11:32 +0200 Thibault Saunier + + * validate/gst/validate/media-descriptor-parser.c: + * validate/gst/validate/media-descriptor.c: + * validate/gst/validate/media-descriptor.h: + validate: media-descriptor: Add a way to specify when a field value is unknown + And this way is to set the attribute to... `unknown` + +2018-05-22 19:43:01 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/media-descriptor-parser.c: + * validate/gst/validate/media-descriptor-writer.c: + * validate/gst/validate/media-descriptor.c: + * validate/gst/validate/media-descriptor.h: + validate: media-check: Also check that segments are correct + +2018-06-14 18:01:54 +0100 Philippe Normand + + * debug-viewer/GstDebugViewer/GUI/window.py: + debug-viewer: Fix reload file action. + Copy the log file only we're loading a file different from the previous file. + The previous version of this code was broken because the existing tmpfile was + removed from disk before being copied to a new temporary file. + +2018-06-05 16:38:10 +0200 Edward Hervey + + * validate/gst/validate/media-descriptor.c: + validate/media-descriptor: Fix indentation + +2018-06-05 16:36:24 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + validate: Add a new issue to detect invalid event seqnum + Events should always have a valid seqnum. Add a new issue which + allows detecting such events. And use that check in the + pad monitor + +2018-06-05 16:25:46 +0200 Edward Hervey + + * validate/.gitignore: + * validate/docs/.gitignore: + * validate/tools/.gitignore: + validate: Update all gitignore + +2018-05-25 12:03:46 +0200 Alicia Boya García + + * validate/launcher/baseclasses.py: + gst-validate-launcher: let gdb handle SIGINT itself + Otherwise both gdb and gst-validate-launcher will react to ^C at the + same time, gdb will be killed by SIGHUP (because gst-validate-launcher + quitted in consequence of the ^C) and the terminal state will be left + garbled because readline inside gdb had disabled echo. + https://bugzilla.gnome.org/show_bug.cgi?id=796396 + +2018-05-25 12:06:22 +0200 Alicia Boya García + + * validate/launcher/baseclasses.py: + gst-validate-launcher: disable timeouts when debugging in gdb interactively + An interactive debugging session can be going for a long time, we don't + want any timeouts in that case. + https://bugzilla.gnome.org/show_bug.cgi?id=796397 + +2018-05-24 18:25:59 +0200 Alicia Boya García + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + gst-validate-launcher: Stop in --gdb by default, add --gdb-non-stop + This patch modifies the default behavior of --gdb to not run and quit + automatically the test, but rather wait for user input. This is + usually much more convenient to debug all kinds of bugs. + The automatic run behavior has been moved to a new command switch: + --gdb-non-stop + https://bugzilla.gnome.org/show_bug.cgi?id=796389 + +2018-05-18 11:50:18 -0400 Thibault Saunier + + * validate/tools/gst-validate.c: + validate: Error out if gst_parse_launch sets an error. + https://bugzilla.gnome.org/show_bug.cgi?id=796240 + +2018-05-15 14:40:45 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Error out loudly if the testlist changes + When --fail-on-testlist-change is set. + +2018-05-15 14:35:30 -0400 Thibault Saunier + + * validate/launcher/reporters.py: + validate:launcher: Do not print time spent if the testsuite never started + +2018-04-28 10:15:17 +0200 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate: launcher: Make sure testsuites are used/configured once only + +2018-05-13 16:30:25 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Add a way to check if a gst feature is present + And make sure iqa is present to run IQA tests. + +2018-05-03 11:27:31 +0200 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + validate:launcher: Cleanup the way we find where -validate tools are + +2018-04-20 23:57:32 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + * validate/launcher/utils.py: + validate: launcher: Add a way to simply run SSIM checks on rendered files + We will run a simple pipeline with the IQA element to run ssim (dssim) + tests on the rendered files, comparing it with a reference file. + For now we use the very empiric 1.0 value as a ssim error threshold and + the goal is basically to detect completely broken renderings. + +2018-04-19 22:13:29 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate: Add support for the new testbin protocol + +2018-04-19 22:13:03 -0300 Thibault Saunier + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI/columns.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + debug-viewer: Ignore broken utf8 errors + Not much we can do if the input file is not perfectly valid UTF8 + but we should just do as good as we can. + +2018-05-13 13:02:11 +0100 Philippe Normand + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI/colors.py: + * debug-viewer/GstDebugViewer/GUI/columns.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + debug-viewer: MEMDUMP debug level support + +2018-05-07 17:30:13 +0200 Edward Hervey + + * validate/launcher/apps/gstvalidate.py: + validate: Remove hls.*seek_with_stop blacklisting + The issue is closed upstream (because of concentrating on decodebin3 + instead), and initial forever testing seems to show the issue doesn't + happen anymore + +2018-05-05 19:55:14 +0530 Nirbheek Chauhan + + * meson.build: + * meson_options.txt: + * validate/meson.build: + meson: Update option names to omit disable_ prefixes + Also yield common options to the outer project (gst-build in our case) + so that they don't have to be set manually. + +2018-04-27 17:32:38 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + validate: pipeline: Handle the case where a pad has no monitor + We do not monitor ghost pads, only real pads, so this is a totally + legitimate case. + https://bugzilla.gnome.org/show_bug.cgi?id=792536 + +2018-04-25 10:57:14 +0800 Kai Kang + + * validate/gst/validate/Makefile.am: + validate: fix out of source tree build error + It fails to generate gst-validate-enum-types.h and gst-validate-enum-types.c + when build out of source tree. Add the path for template files. + https://bugzilla.gnome.org/show_bug.cgi?id=795531 + Signed-off-by: Kai Kang + +2018-04-19 14:26:23 -0300 Thibault Saunier + + * debug-viewer/GstDebugViewer/Common/Main.py: + debug-viewer: Fix raising unhandled exception + Old code was uselessly complex + +2018-04-19 08:44:50 -0300 Thibault Saunier + + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + debug-viewer: Fix stacktrace after port to py3 + +2018-04-18 09:34:57 -0300 Thibault Saunier + + * debug-viewer/GstDebugViewer/GUI/window.py: + debug-viewer: Copy log files in temporaries before using them + They are mmap'ed and it gets wrong if the file is changed. + There is high probablility the user will generate new logs while + inspecting some logs in the same file + +2018-04-18 09:03:01 -0300 Thibault Saunier + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + debug-viewer: Add a shortcut to show/hide timeline + +2018-04-15 16:31:36 -0300 Thibault Saunier + + * debug-viewer/GstDebugViewer/Common/Main.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/GstDebugViewer/__init__.py: + * debug-viewer/data/meson.build: + * debug-viewer/gst-debug-viewer: + * debug-viewer/meson.build: + * debug-viewer/org.freedesktop.GstDebugViewer.desktop.in: + * debug-viewer/po/LINGUAS: + * debug-viewer/setup.cfg: + * debug-viewer/setup.py: + * meson.build: + * meson_options.txt: + debug-viewer: Port to meson + This allows us to run unit test as part of ninja test and have versionning + in sync. Also the goal is to have everything inside meson. + https://bugzilla.gnome.org/show_bug.cgi?id=795282 + +2018-04-15 20:47:36 -0300 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + * validate/launcher/baseclasses.py: + validate:launcher: Add support for specifying a workdir in tests + +2018-04-15 19:45:43 -0300 Thibault Saunier + + * validate/launcher/utils.py: + validate:launcher: printc is accepting object as arguments + Make sure in all code paths those are converted to strings + +2018-04-15 11:28:33 +0100 Philippe Normand + + * debug-viewer/GstDebugViewer/Common/Data.py: + debug-viewer: Dispatcher source ID clean-up + This patch fixes this runtime warning: + GstDebugViewer/Common/Data.py:67: Warning: Source ID 17 was not found when attempting to remove it + GObject.source_remove(self.source_id) + +2018-04-14 16:04:22 +0100 Philippe Normand + + * debug-viewer/GstDebugViewer/Common/Main.py: + * debug-viewer/GstDebugViewer/Common/utils.py: + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI/__init__.py: + * debug-viewer/GstDebugViewer/GUI/columns.py: + * debug-viewer/GstDebugViewer/GUI/filters.py: + * debug-viewer/GstDebugViewer/GUI/models.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/GstDebugViewer/Main.py: + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + * debug-viewer/GstDebugViewer/tests/__init__.py: + * debug-viewer/GstDebugViewer/tests/create-test-log.py: + * debug-viewer/GstDebugViewer/tests/performance.py: + * debug-viewer/GstDebugViewer/tests/test_models.py: + * debug-viewer/setup.py: + * debug-viewer/tests/test_models.py: + * hooks/pre-commit-python.hook: + debug-viewer: PEP8 all the things + +2018-04-14 14:22:11 +0100 Philippe Normand + + * debug-viewer/GstDebugViewer/Common/Data.py: + * debug-viewer/GstDebugViewer/Common/GUI.py: + * debug-viewer/GstDebugViewer/Common/Main.py: + * debug-viewer/GstDebugViewer/Common/__init__.py: + * debug-viewer/GstDebugViewer/Common/utils.py: + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI/__init__.py: + * debug-viewer/GstDebugViewer/GUI/app.py: + * debug-viewer/GstDebugViewer/GUI/columns.py: + * debug-viewer/GstDebugViewer/GUI/models.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/GstDebugViewer/Main.py: + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + * debug-viewer/gst-debug-viewer: + * debug-viewer/setup.py: + * debug-viewer/tests/create-test-log.py: + * debug-viewer/tests/performance.py: + * debug-viewer/tests/test_models.py: + debug-viewer: Port to Python3 + And fix unit-tests. + https://bugzilla.gnome.org/show_bug.cgi?id=795260 + +2018-04-14 11:27:48 +0100 Philippe Normand + + * debug-viewer/GstDebugViewer/Plugins/ColorizeRows.py: + * debug-viewer/GstDebugViewer/Plugins/FileProperties.py: + debug-viewer: remove broken/unimplemented plugins + +2018-04-12 23:24:16 -0300 Thibault Saunier + + * hooks/pre-commit-python.hook: + Update python hook with the new pycodestyle + +2018-04-12 23:11:04 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Add a method to find tests in a TestManager + +2018-04-12 23:05:01 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Extract method to set a validate configuration on tests + +2018-04-12 23:03:04 -0300 Thibault Saunier + + * validate/gst/validate/validate.c: + validate: Handle having a list of structure based/file path configs + +2018-03-23 20:58:38 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/utils.py: + launcher: Print test number in the order they finish + Instead of the test index in the list of tests as it is + meaningless to the user and feels weird. + Also minor fix in the test name display when running with --forever. + +2018-03-23 18:02:43 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + launcher: Minor indentation issue fixes + +2018-03-23 17:44:06 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/reporters.py: + * validate/launcher/utils.py: + launcher: Clean up outpout + Make our stdout output simpler to follow by: + - Not printing the tests we launch (it is not really useful in the end) + - Using `\r` when printing the passed tests + - Not reprinting all the test in a now useless summary + +2018-03-18 10:38:42 -0300 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + check: Use meson introspect to list meson tests + +2018-03-20 10:56:57 +0000 Tim-Philipp Müller + + * meson.build: + * validate/NEWS: + * validate/RELEASE: + * validate/configure.ac: + Back to development + +=== release 1.14.0 === + +2018-03-19 20:29:07 +0000 Tim-Philipp Müller + + * meson.build: + * validate/ChangeLog: + * validate/NEWS: + * validate/configure.ac: + * validate/gst-validate.doap: + Release 1.14.0 + +2018-03-13 21:14:51 -0300 Thibault Saunier + + * meson.build: + validate: Fix the way we set the testsuite version + The testuite version should be 'master' during development + and the version number on releases, during the pre-release + cycle, there is no nano version, thus our detection handling + was mistaking. + +2018-03-13 22:20:52 +0000 Tim-Philipp Müller + + * meson.build: + meson: update version + +=== release 1.13.91 === + +2018-03-13 19:30:43 +0000 Tim-Philipp Müller + + * validate/ChangeLog: + * validate/NEWS: + * validate/configure.ac: + * validate/gst-validate.doap: + Release 1.13.91 + +2018-03-13 13:58:07 +0000 Tim-Philipp Müller + + * validate/docs/validate/meson.build: + * validate/gst/validate/Makefile.am: + * validate/gst/validate/gst-validate-bin-monitor.h: + * validate/gst/validate/gst-validate-element-monitor.h: + * validate/gst/validate/gst-validate-enum-types.h.template: + * validate/gst/validate/gst-validate-media-info.h: + * validate/gst/validate/gst-validate-monitor-factory.h: + * validate/gst/validate/gst-validate-monitor.h: + * validate/gst/validate/gst-validate-override-registry.h: + * validate/gst/validate/gst-validate-override.h: + * validate/gst/validate/gst-validate-pad-monitor.h: + * validate/gst/validate/gst-validate-pipeline-monitor.h: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-reporter.h: + * validate/gst/validate/gst-validate-runner.h: + * validate/gst/validate/gst-validate-scenario.h: + * validate/gst/validate/gst-validate-utils.h: + * validate/gst/validate/media-descriptor-parser.h: + * validate/gst/validate/media-descriptor-writer.h: + * validate/gst/validate/media-descriptor.h: + * validate/gst/validate/meson.build: + * validate/gst/validate/validate-prelude.h: + * validate/gst/validate/validate.h: + validate: GST_EXPORT -> GST_VALIDATE_API + We need different export decorators for the different libs. + For now no actual change though, just rename before the release, + and add prelude headers to define the new decorator to GST_EXPORT. + +2018-03-13 13:48:00 +0100 Xabier Rodriguez Calvar + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + debug-viewer: solved crash when maximum freq sentinel is 0 + https://bugzilla.gnome.org/show_bug.cgi?id=794282 + +2018-03-12 12:11:18 -0300 Thibault Saunier + + * debug-viewer/GstDebugViewer/GUI/window.py: + debug-viewer: Fix copying current line + +2018-03-12 11:18:02 -0300 Thibault Saunier + + * debug-viewer/MANIFEST.in: + * debug-viewer/org.freedesktop.GstDebugViewer.appdata.xml.in: + * debug-viewer/setup.cfg: + debug-viewer: Add an appdata file + +=== release 1.13.90 === + +2018-03-03 22:54:57 +0000 Tim-Philipp Müller + + * validate/ChangeLog: + * validate/NEWS: + * validate/configure.ac: + * validate/gst-validate.doap: + Release 1.13.90 + +2018-03-01 18:49:21 +0100 Mathieu Duponchelle + + * meson.build: + meson: enable more warnings + +2018-02-07 10:13:44 +0100 Xabier Rodriguez Calvar + + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/data/menus.ui: + debug-viewer: fix names of actions/functions + https://bugzilla.gnome.org/show_bug.cgi?id=793241 + +2018-02-07 10:05:35 +0100 Xabier Rodriguez Calvar + + * debug-viewer/GstDebugViewer/GUI/filters.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/data/menus.ui: + debug-viewer: Added filter for threads + https://bugzilla.gnome.org/show_bug.cgi?id=793241 + +2018-02-07 09:52:26 +0100 Xabier Rodriguez Calvar + + * debug-viewer/GstDebugViewer/GUI/filters.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/data/menus.ui: + debug-viewer: Added filter for function + https://bugzilla.gnome.org/show_bug.cgi?id=793241 + +2018-02-21 19:50:33 +0000 Tim-Philipp Müller + + * meson.build: + meson: simplify GST_DISABLE_GST_DEBUG check and don't use add_global_* + add_global_arguments() can't be used in subprojects. It's + entirely possible that devtools is a subproject but gstreamer + is picked up from an installed location, so we should + really use add_project_arguments() in both cases. + +2018-02-19 12:02:04 +0000 Tim-Philipp Müller + + * validate/tools/meson.build: + validate: tools: fix build dependencies for validate-rtsp-server + In file included from ../subprojects/gst-devtools/validate/tools/gst-validate-rtsp-server.c:21:0: + .../gst/gst.h:31:10: fatal error: gst/gstenumtypes.h: No such file or directory + +2018-02-18 12:21:34 +0200 Sebastian Dröge + + * validate/plugins/gtk/gstvalidategtk.c: + validategtk: Stop using deprecated keymap API + gstvalidategtk.c:184:7: error: ‘gdk_keymap_get_default’ is deprecated: Use 'gdk_keymap_get_for_display' instead [-Werror=deprecated-declarations] + gdk_keymap_get_entries_for_keyval (gdk_keymap_get_default (), + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +2018-02-14 13:30:41 +0100 Xabier Rodriguez Calvar + + * debug-viewer/GstDebugViewer/Data.py: + debug-viewer: Fixed C++ destructors detection + https://bugzilla.gnome.org/show_bug.cgi?id=793447 + +2018-02-15 21:00:57 +0000 Tim-Philipp Müller + + * meson.build: + * validate/configure.ac: + Fix versions + +2018-02-15 19:44:37 +0000 Tim-Philipp Müller + + * meson.build: + * validate/configure.ac: + Back to development + +=== release 1.13.1 === + +2018-02-15 17:30:08 +0000 Tim-Philipp Müller + + * meson.build: + * validate/NEWS: + * validate/configure.ac: + * validate/gst-validate.doap: + Release 1.13.1 + +2018-02-15 18:27:37 +0000 Tim-Philipp Müller + + * validate/gst/validate/Makefile.am: + validate: dist enum types templates + +2018-02-13 14:02:39 +0100 Xabier Rodriguez Calvar + + * debug-viewer/GstDebugViewer/Data.py: + debug-viewer: Fix C++ detection of lambdas as function + https://bugzilla.gnome.org/show_bug.cgi?id=793422 + +2018-02-10 14:20:44 +0100 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + validate-pad-monitor: Use GST_SEQNUM_INVALID + Instead of 0 (which is valid) + +2018-02-08 08:20:55 -0700 Olivier Crête + + * debug-viewer/GstDebugViewer/Data.py: + debug-viewer; Store thread as long instead of int + On 64-bit platforms, the thread id can be over 2^32 so use a long + to handle it. + +2017-12-29 11:25:05 +0900 Wonchul Lee + + * validate/tools/gst-validate-images-check.c: + * validate/tools/meson.build: + tools: gst-validate-images-check: Fix typo + https://bugzilla.gnome.org/show_bug.cgi?id=792035 + +2018-01-30 20:36:00 +0000 Tim-Philipp Müller + + * meson.build: + meson: use -fno-strict-aliasing where supported + https://bugzilla.gnome.org/show_bug.cgi?id=769183 + +2017-12-18 10:51:05 +0100 Edward Hervey + + * validate/launcher/baseclasses.py: + validate/baseclasses: Release un-needed data when test ends + This was keeping around 500-700kB of data for each test, which was + gradually raising memory usage of a full run by 100MB+ + The reports are definitely not needed, and we only need to keep + information from the subprocess env variable that we might need + later on for final reporting + +2017-12-18 09:48:21 +0100 Edward Hervey + + * validate/launcher/baseclasses.py: + validate/baseclasses: Don't leak several hundred MB of XML + The xml-based MediaDescriptor were keeping open the XML file and the + associated ElementTree structures, resulting in memory usage of several + hundred megabytes. + Instead cache the information we need immediately and release the + XML structure + +2017-12-17 16:22:51 -0500 Nicolas Dufresne + + * validate/data/scenarios/meson.build: + meson: Add missing force_rtsp2 scenario + Without this file, gst-validate installed using meson will fail all + RTSP2 tests + +2017-12-03 12:23:51 +0100 Edward Hervey + + * validate/launcher/apps/gstvalidate.py: + validate: Remove protocol-specific timeouts + Since we now check position/status of pipeline at regular intevals, + we no longer need to impose a different timeout based on the + protocol used. + Avoids having 4min long timeouts for no reason (30s is enough) + +2017-12-03 10:42:49 +0100 Edward Hervey + + * validate/gst/validate/gst-validate-report.c: + * validate/launcher/baseclasses.py: + validate: Use a single TCPServer for subprocess communication + Instead of creating a separate TCPServer for each test, just create + one which handles all connections in a threaded fashion. + Shaves off ~500ms per test + https://bugzilla.gnome.org/show_bug.cgi?id=791159 + +2017-12-03 10:49:22 +0100 Edward Hervey + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + * validate/launcher/reporters.py: + validate-launcher: Allow running tests out-of-order + When the --shuffle option is used, the tests will be run out of order. + This optimizes CPU utilization since it allows running synchronized + and unsynchronized tests at the same. + +2017-12-03 11:07:00 +0100 Edward Hervey + + * validate/launcher/utils.py: + validate: Reduce time waiting for subprocess to stop + stopping the subprocess is done from the main thread, this would + throttle starting/stopping any tests by one second. + Start with 50ms, and gradually increase the wait between iterations + +2017-12-03 11:05:40 +0100 Edward Hervey + + * validate/launcher/apps/gstvalidate.py: + gstvalidate: Lower timeout to check for rtsp-server to be up + Check every 100ms, avoids throttling all rtsp tests by 500ms + +2017-12-02 09:36:27 -0300 Thibault Saunier + + * .gitignore: + * validate/launcher/baseclasses.py: + validate:launcher: Launch tests in `_TestsLauncher` not in TestsManagaer + So that Test from several TestManager can run in parallel and thus avoid + waiting for tests from one TestManager to run the following one., + Also by design TestsLauncher should always have been the responsible for + ... launching tests. + +2017-11-25 13:10:41 +0100 Edward Hervey + + * validate/gst/validate/media-descriptor.c: + validate: Don't leak strings + We only use them in the error/debug case anyway + +2017-11-25 12:46:05 +0100 Edward Hervey + + * validate/gst/validate/gst-validate-report.c: + validate-report: Plug leaks + The trace was never freed, nor were the output of g_str_split + +2017-11-23 12:27:11 +0100 Edward Hervey + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: Don't assume element have factories + Some elements might not originate from factories (like custom/internal + elements). + Avoids dereferencing a NULL pointer + +2017-11-22 16:35:46 +0100 Edward Hervey + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: Handle non-relative switch + Make sure we stay within the number of present streams (and avoid + out-of-bound read). + CID #1415470 + +2017-11-22 16:34:42 +0100 Edward Hervey + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: Handle switching stream of type not present + Unlikely to happen, but at least don't end up doing unsafe calculation + with n == 0 afterwards + CID #1415453 + +2017-11-12 20:08:39 +0530 Nirbheek Chauhan + + * validate/tools/meson.build: + meson: Always require the latest gst-rtsp-server + In the worst case, when building with gst-uninstalled, we will try to + link against an older gst-rtsp-server provided by the system. Found by + philn. + +2017-11-08 17:22:47 +0100 Edward Hervey + + * validate/gst/validate/Makefile.am: + validate: Call g-ir-scanner with the same toolchain as the rest + +2017-10-27 09:59:53 +0200 Edward Hervey + + * validate/tools/gst-validate-analyze: + validate-analyze: Update for xml format changes + +2017-07-03 16:36:32 -0400 Thibault Saunier + + * validate/data/scenarios/force_rtsp2.scenario: + * validate/launcher/apps/gstvalidate.py: + validate: launcher: Run rtsp tests against both V1 and V2 + https://bugzilla.gnome.org/show_bug.cgi?id=781446 + +2017-09-06 16:35:25 -0300 Thibault Saunier + + * validate/launcher/main.py: + validate:launcher: Allow disabling using the number of failed tests as exitcode + This is usefull on CI servers where the test results will be inspected + and the status of the build built from it. + +2017-08-24 14:17:08 +0900 Jimmy Ohn + + * validate/launcher/main.py: + validate: launcher: Modify the order of the parser argument + Modify the order of the parser argument before setting dir_group + https://bugzilla.gnome.org/show_bug.cgi?id=786715 + +2017-08-26 10:50:44 -0300 Thibault Saunier + + * validate/launcher/utils.py: + launcher: Automatically disable output coloration if not supported + +2017-08-26 09:10:40 -0300 Thibault Saunier + + * validate/launcher/meson.build: + meson: Fix the way we set the testsuite version + +2017-08-18 11:37:28 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + * validate/launcher/reporters.py: + validate:launcher: Use the number of failed test as exit code + We used to always return 0, which was not right! + +2017-08-14 16:39:56 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + launcher: Avoid exceptions when inspecting renders files + We were a bit to strict on the Exception types which lead to + the launcher failling itself when it shouldn't + +2017-08-12 12:08:09 +0100 Tim-Philipp Müller + + * meson.build: + meson: hide symbols by default unless explicitly exported + +2017-08-12 12:04:42 +0100 Tim-Philipp Müller + + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/win32/common/libgstvalidate.def: + validate: hide some private symbols + +2017-08-10 21:43:54 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + validate: Plug a potential leak when retrieving peer pad + +2017-08-10 19:25:09 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + validate: Fix going over ghostpads/proxypads + +2017-08-10 14:35:09 +0100 Tim-Philipp Müller + + * validate/docs/validate/meson.build: + * validate/launcher/meson.build: + * validate/plugins/gtk/meson.build: + meson: fix a few meson warnings + WARNING: The variable(s) 'DATADIR', 'LIBDIR' in the input file + 'subprojects/gst-devtools/validate/launcher/config.py.in' are not + present in the given configuration data + WARNING: Passed invalid keyword argument "scanobj_args". This will + become a hard error in the future. + WARNING: Keyword argument "install" defined multiple times. This + will be a an error in future Meson releases. + +2017-08-07 16:00:53 -0400 Thibault Saunier + + * validate/win32/common/libgstvalidate.def: + validate:win32: Update .def file. + +2017-08-07 15:56:21 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-bin-monitor.h: + * validate/gst/validate/gst-validate-element-monitor.h: + * validate/gst/validate/gst-validate-media-info.h: + * validate/gst/validate/gst-validate-monitor-factory.h: + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-monitor.h: + * validate/gst/validate/gst-validate-override-registry.h: + * validate/gst/validate/gst-validate-override.h: + * validate/gst/validate/gst-validate-pad-monitor.h: + * validate/gst/validate/gst-validate-pipeline-monitor.h: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-reporter.h: + * validate/gst/validate/gst-validate-runner.h: + * validate/gst/validate/gst-validate-scenario.h: + * validate/gst/validate/gst-validate-utils.h: + * validate/gst/validate/media-descriptor-parser.h: + * validate/gst/validate/media-descriptor-writer.h: + * validate/gst/validate/media-descriptor.h: + * validate/gst/validate/validate.h: + * validate/win32/common/libgstvalidate.def: + validate: Mark symbols explicitly for export with GST_EXPORT + With an exception: + * gst_validate_monitor_setup + which was never declared in headers and should always have been static. + +2017-07-26 17:22:33 -0400 Thibault Saunier + + * validate/gst/validate/Makefile.am: + validate: Fix building the tracer + It fails on some platforms, I guess this is the reason + +2017-07-26 16:15:16 -0400 Thibault Saunier + + * validate/gst/validate/Makefile.am: + * validate/gst/validate/gst-validate-enum-types.c.template: + * validate/gst/validate/gst-validate-pipeline-monitor.c: + validate: Also mkenums with autotools + And fix the build with stricter gcc arguments. + +2017-07-25 11:23:35 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-bin-monitor.h: + * validate/gst/validate/gst-validate-enum-types.c.template: + * validate/gst/validate/gst-validate-enum-types.h.template: + * validate/gst/validate/gst-validate-enums.h: + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-monitor.h: + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/gst/validate/gst-validate-pipeline-monitor.h: + * validate/gst/validate/meson.build: + * validate/gst/validate/validate.h: + * validate/tools/gst-validate.c: + validate: Add a way to print information about pipeline status + Similare to what is done with gst-launch. + And finally generate GTypes for our flags and enums. + +2017-07-26 15:22:49 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: Fix NULL pointer usage + for good this time ... + CID #1415570 + +2017-07-26 15:18:57 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-report.c: + validate-report: Fix a leak in error cases + CID #1415494 + +2017-07-25 09:55:02 +0200 Edward Hervey + + * validate/launcher/apps/gstvalidate.py: + validate: Re-enable mxf op2b tests + https://bugzilla.gnome.org/show_bug.cgi?id=785119 + +2017-07-21 10:30:37 -0400 Thibault Saunier + + * validate/win32/common/libgstvalidate.def: + wind32: Update .def file. + +2017-07-20 14:21:59 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: Protect against priv NULL usage + CID #1415570 + +2017-07-19 12:16:53 -0400 Thibault Saunier + + * validate/data/scenarios/Makefile.am: + * validate/data/scenarios/meson.build: + validate: Do not install now removed setup_sink_props_max_lateness.scenario file + +2017-07-19 11:49:09 -0400 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + * validate/launcher/testsuites/check.py: + validate:launcher:check: Make sure to register tests from the testsuite + Instead of having them listed from the app manager. This is needed + to avoid backtrace as tests now have to be register when setting up + the testsuite. + +2017-07-19 11:27:13 -0400 Thibault Saunier + + * validate/data/scenarios/setup_sink_props_max_lateness.scenario: + * validate/data/valgrind.config: + * validate/gst/validate/gst-validate-element-monitor.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/gst-validate-utils.h: + * validate/launcher/baseclasses.py: + validate: Factor out a method to set properties on elements in utils + Make sure to use it where appropriate and add some logging when + setting an object property from an action. + And use the valgrind.conf to set all the properties instead of having + a mixture of a config scenario and the config file (making sure the + max-lateness is set on any sink) + +2017-07-19 10:52:40 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario: Allow not config action to be executed from config files + When those are special cased to support that, such as the `set-property` + action. + This special handling was added in + 4927c657107dd23405456a703bb23173ab60f27d + validate: disable QOS features when running with valgrind + before we started to support executing arbitrary config action from + configuration files. + +2017-07-19 10:17:25 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: scenario: Fix running config action from the config file + +2017-07-19 15:47:28 +0200 Edward Hervey + + * validate/launcher/apps/gstvalidate.py: + validate: Blacklist op2b mxf files + See https://bugzilla.gnome.org/show_bug.cgi?id=785119 + +2017-07-19 11:02:44 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/media-descriptor-writer.c: + validate: Cast GList data content before usage + Apart from code readability, it allows compilers to detect wrong usages, + such as the call to gst_validate_action_new() which was using the wrong + argument + +2017-07-18 12:09:13 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Error out if no testsuite could be loaded + +2017-07-13 16:43:32 -0400 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + validate: launcher: Namespace test name with the testsuite name + Also allowing users to pass test names directly + +2017-07-13 20:17:51 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate: launcher: Properly use TestsLauncher.list_test to load tests + Otherwise we might skip check_defined_tests. + +2017-07-18 10:47:00 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + validate: Remove some dead code + CID 1415457 + +2017-07-18 10:45:29 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + validate: Plug a minor string leak + CID 1415459 + +2017-07-18 10:42:00 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + validate: Don't create scenario on a monitor which has no target + Not very probable but avoids a potential NULL pointer dereferencing. + CID 1415460 + +2017-07-18 10:36:34 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Always only use the first description in scenarios + Also pluging a leak of the descrption copied structure + CID 1415463 + +2017-07-18 10:27:49 -0400 Thibault Saunier + + * validate/plugins/ssim/gstvalidatessim.c: + validate: ssim: Do not compare unsigned to < 0 + CID 1415473 + +2017-07-18 10:23:31 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + validate: Plug a string leak + CID 1415475 + +2017-07-18 10:19:23 -0400 Thibault Saunier + + * validate/gst-libs/gst/video/gstvalidatessim.c: + validate:ssim: Let user know when no file have been compared + Fixing a possible division by zero issue. + CID 1415482 + +2017-07-18 10:16:07 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-runner.c: + validate: Add missing break statement + CID 1415485 + +2017-07-18 10:14:59 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-media-info.c: + Check g_file_set_contents() return value + CID 1415486 + +2017-07-18 10:12:07 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-reporter.c: + validate: Plug leak of copy of a va_list + CID 1415490 + +2017-07-18 10:07:34 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-element-monitor.c: + validate: Do not check NULL pointer uselessly + CID 141593 + +2017-07-18 10:05:02 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + validate: Plug minor leak in issue creation error path + CID 1415494 + +2017-07-18 10:00:03 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-media-info.c: + validate: Remove some dead code + Next will never be NULL as `done` is always set to TRUE when next is + set. + CID 1415503 + +2017-07-18 09:51:16 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-monitor.c: + validate: monitor: Add missing break; statement + Fixes CID 1415500 + +2017-07-18 15:50:35 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: Fix wrong return value + We were always returning ok ... + CID #1415484 + +2017-07-18 15:48:01 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: Check g_file_set_contents() return value + CID #1415487 + +2017-07-18 15:45:13 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: Fix copy/paste error + CID #1415502 + +2017-07-18 15:43:26 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: Fix string usage + Use the string representation of the index if it *IS* present (and + not the opposite). + CID #1415506 + +2017-07-18 15:38:04 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-utils.c: + validate-utils: Add NULL check + Unlikely to be triggered. And fix typo at the same time + CID #1415464 + +2017-07-14 10:36:49 +0300 Sebastian Dröge + + * validate/launcher/apps/gstvalidate.py: + validate: Un-blacklist scrub_forward_seeking.op2b-mpeg2-wave_hd_mxf + It works now after various mxfdemux changes. + https://bugzilla.gnome.org/show_bug.cgi?id=764025 + +2017-07-13 08:45:28 +0200 Edward Hervey + + * validate/launcher/apps/gstvalidate.py: + gstvalidate: Re-allow tests that should be fixed + +2017-07-12 14:46:36 +0200 Edward Hervey + + * validate/tests/check/validate/test-utils.c: + check: Remove dead assignments + +2017-07-11 10:11:33 -0400 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + * validate/launcher/baseclasses.py: + validate: launcher: Add some missing env variables in command to launch test + +2017-07-07 12:26:40 +0100 Tim-Philipp Müller + + * meson.build: + meson: find python3 via python3 module + https://bugzilla.gnome.org/show_bug.cgi?id=783198 + +2017-06-30 12:32:56 -0400 Thibault Saunier + + * validate/tools/gst-validate.c: + validate: Recalculate latency on LATENCY messages + +2017-06-30 12:30:40 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Allow setting properties by element factory name + +2017-06-30 09:46:57 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Enhance the set_property action to handle enum props + User needs to specify the enum value as a string, to be used + as with gst_util_set_object_arg. + Also enhance reporting and verify that the set value has actually + been taken into account. + +2017-06-30 09:45:02 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + validate: Use Gst printing utils in our reporting system + Allowing us to use GST_PTR_FORMAT and friends! + +2017-06-28 15:54:13 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Do not check ModuleNotFound exception + It is a subclass of ImportError and is avalaible only since 3.6 + https://ci.gstreamer.net/job/pitivi-flatpak/626/console + +2017-06-28 13:01:47 -0400 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: Disable seek with stop on RTSP streams + It is actually not supported + +2017-06-23 16:20:01 -0400 Thibault Saunier + + * meson.build: + meson: Allow using glib as a subproject + +2017-06-22 15:26:08 -0400 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: Avoid useless and expensive deep copies + +2017-06-22 13:08:30 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Speed up xml parsing using lxml if avalaible + +2017-06-22 12:01:12 -0400 Thibault Saunier + + * validate/data/scenarios/disable_subtitle_track_while_paused.scenario: + validate: make swicthing subtitle track while paused require prerolling + +2017-06-22 11:53:49 -0400 Thibault Saunier + + * validate/data/scenarios/change_state_intensive.scenario: + validate: Fix the change_state_intensive scenario + The scenario was in no way certified that the pipeline was in PAUSED + state when starting on an stream that does not preroll + +2017-06-21 14:36:33 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Enhance playbin3 stream selection error message + +2017-06-20 15:51:27 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + validate: Add missing space in segment mismatch issue description + +2017-06-20 10:43:54 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Enhance command printing when using a server + So it can be copy pasted and work + +2017-06-20 10:43:09 -0400 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: No need to use -validate as a tracer for RTSP server + It is now linked into the server app + +2017-06-16 17:31:19 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Fix launching testsuite with relative paths + +2017-06-13 17:13:22 -0400 Thibault Saunier + + * validate/plugins/extra_checks/gstvalidateextrachecks.c: + * validate/plugins/extra_checks/meson.build: + * validate/plugins/meson.build: + validate: Add a plugin with potential extra checks + And add a way to check that a configured number of instances of a particular + element is used, this is useful to make sure for example that playing a + particular stream doesn't lead to several decoders being instanciated. + +2017-06-13 17:11:40 -0400 Thibault Saunier + + * validate/gst/validate/validate.c: + validate: Fix the way we check if _CONFIG is a list of structures + +2017-06-13 16:15:20 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-override.c: + * validate/gst/validate/gst-validate-override.h: + validate: overrides: Add a hook about newly added elements in a bin + +2017-06-13 16:08:23 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-monitor-factory.c: + * validate/gst/validate/gst-validate-monitor.c: + validate: Attach overrides before calling monitor.setup() + +2017-06-08 13:43:41 -0700 Reynaldo H. Verdejo Pinochet + + * validate/gst-libs/gst/video/gstvalidatessim.c: + validate: libs: video: improve ssim's action registrations + +2017-06-08 12:30:22 -0400 Thibault Saunier + + * validate/tools/Makefile.am: + validate: Do not link the rtsp server against validatevideo + It is not needed + https://bugzilla.gnome.org/show_bug.cgi?id=783554 + +2017-06-08 12:16:24 -0400 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: Do not which(None), the rtsp-server command is unset if not avalaible + https://bugzilla.gnome.org/show_bug.cgi?id=783551 + +2017-06-07 16:18:59 -0400 Thibault Saunier + + * validate/tools/meson.build: + meson: Make dependency on rtsp-server really optionnal + +2017-06-07 15:06:10 -0400 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + * validate/launcher/baseclasses.py: + validate:launcher: Handle test that can't be run in parralel + +2017-06-07 12:29:17 -0400 Thibault Saunier + + * validate/tests/check/meson.build: + meson: Do not use path separator in test names + Avoiding warnings like: + WARNING: Target "elements/audioamplify" has a path separator in its name. + +2017-06-07 09:16:45 -0400 Thibault Saunier + + * validate/configure.ac: + * validate/launcher/apps/gstvalidate.py: + * validate/tools/Makefile.am: + * validate/tools/gst-validate-rtsp-server.c: + * validate/tools/meson.build: + validate: Add 'our own' RTSP server implementation + +2017-06-06 16:23:48 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Handle optional tests + For example RTSP tests might not be avalaible if gst-rtsp-server-example-uri is not avalaible + +2017-06-06 12:45:31 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Print logfiles when printing test result + It was often annoying to check wrong logs because the result are + not printed near the reference to logs. + +2017-06-06 12:42:57 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/validate.c: + validate: Add a way to use config actions in GST_VALIDATE_CONFIG files + The synthax is: + core, action=action-name, param1=1, param2=param2 + +2017-06-05 11:35:43 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Fallback to using media_info to determine stream duration + In the case the query duration return CLOCK_TIME_NONE. + +2017-05-10 08:12:18 -0300 Thibault Saunier + + * validate/data/scenarios/scrub_backward_seeking.scenario: + * validate/data/scenarios/scrub_backward_seeking_full.scenario: + * validate/data/scenarios/scrub_forward_seeking.scenario: + * validate/data/scenarios/scrub_forward_seeking_full.scenario: + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + validate:launcher: Do not run scenarios that need prerolling on RTSP stream + +2017-04-28 17:59:21 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/meson.build: + validate: Do not print \r in actual files + +2014-12-05 12:16:36 +0100 Thibault Saunier + + * validate/data/scenarios/switch_audio_track_while_paused.scenario: + * validate/data/scenarios/switch_subtitle_track_while_paused.scenario: + * validate/docs/validate-design.txt: + * validate/gst/validate/media-descriptor-writer.c: + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + * validate/launcher/utils.py: + validate: Implement RTSP support + +2017-06-06 23:39:21 -0700 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-reporter.c: + validate: reporter: add doc entry for gst_validate_report()'s varargs + Avoids GTK-Doc parser warnings + +2017-06-06 23:01:32 -0700 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-scenario.c: + validate: scenario: fix typo/grammar issues in function documentation + +2017-06-06 20:56:29 -0700 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-scenario.c: + validate: actions: drop needless newline before feature-rank and wait + Stale new-lines messed the help output of gst-validate -t + +2017-06-06 20:25:10 -0700 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-scenario.c: + validate: actions: add info on mandatory fields for set-property + Additionally, drop a comment that becomes redundant after adding this + info to the action description + +2017-06-06 19:38:23 -0700 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-scenario.c: + validate: scenario: fix description for 'seek' and 'stop' + +2017-06-06 18:50:47 -0700 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/validate.c: + validate: fix documentation for gst_validate_init() + +2017-06-02 16:51:21 -0400 Thibault Saunier + + * validate/win32/common/libgstvalidate.def: + validate: Update win32 def file + +2017-06-01 16:38:25 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-element-monitor.c: + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-monitor.h: + * validate/gst/validate/gst-validate-override.c: + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/media-descriptor.c: + * validate/plugins/ssim/gstvalidatessim.c: + validate: Make Reporter.runner a MT safe weak reference + It can be used in any thread! + +2017-05-31 14:06:04 -0400 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: Add missing is_live implementation for FakeMediaDescriptor + +2017-05-30 17:42:07 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-scenario.c: + validate: Fix json serialized object leaks + +2017-05-30 16:15:19 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Handle not redirecting valgrind output + +2017-05-30 16:14:51 -0400 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + validate:launcher: Add a way to pass arguments to the leak tracer + +2017-05-30 16:13:08 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-bin-monitor.h: + * validate/gst/validate/gst-validate-element-monitor.c: + * validate/gst/validate/gst-validate-element-monitor.h: + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-monitor.h: + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.h: + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/plugins/ssim/gstvalidatessim.c: + validate: Use GWeakRefs on monitor target and pipeline + Making it thread safe and more future proof (though having them point + to NULL might not be handled all around). + https://bugzilla.gnome.org/show_bug.cgi?id=782784 + +2017-05-18 15:21:41 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-monitor.c: + validate: monitor: Unref our weak reference to the pipeline + +2017-04-28 18:02:05 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/plugins/gtk/gstvalidategtk.c: + * validate/tests/check/validate/monitoring.c: + * validate/tests/check/validate/padmonitor.c: + * validate/tools/gst-validate.c: + validate: Make accessing Scenario.pipeline thread safe + The fact that Scenario.pipeline was not accessible in a thread way lead + to the fact that all users had to take the unref the last pipeline ref + in the main thread, otherwise we were crying. This was an ugly + restriction which lead to issue when using scenario on gst-rtsp-server. + This break the API as this commit remove the GstValidateScenario.pipeline + field but it is worth it. + +2017-05-25 15:57:33 +0200 Edward Hervey + + * validate/data/scenarios/change_state_intensive.scenario: + * validate/data/scenarios/full_live_rewind.scenario: + * validate/data/scenarios/play_15s_live.scenario: + * validate/data/scenarios/seek_end_live.scenario: + scenarios: Add/Update scenarios for live contents + +2017-05-25 15:50:23 +0200 Edward Hervey + + * validate/launcher/baseclasses.py: + validate: Implement Scenario.__repr__ + Allows better debugging when looking at logs + +2017-05-25 13:55:52 +0200 Edward Hervey + + * validate/launcher/baseclasses.py: + validate: Add live-related features to scenarios and medias + Note: The notion of "live" here is in the *content* sense and not in the + GStreamer sense. + Ex: + * A rtsp stream is always "live" in the GStreamer sense but might not always + provide live content. + * HLS/DASH streams are not "live" in the GStreamer sense but might + provide "live" content. + Some scenarios might: + * require live content + * not be compatible with live content + This patch adds two new properties for scenarios: + * live_content_required (default False) for scenarios that can only work with + live content. + * live_content_compatible (default False) for scenarios that can work with + both live and non-live content. + This patch adds support for reading a "live" property from stream_info + +2017-05-20 12:26:31 +0200 Víctor Manuel Jáquez Leal + + * validate/gst/validate/validate.c: + validate: g_object_newv() is deprecated + Since glib version 2.54, g_object_newv() is deprecated. + This patch changes that function with a simpler g_object_new(), + since no properties are set. + https://bugzilla.gnome.org/show_bug.cgi?id=782860 + +2017-05-05 14:57:20 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-report.c: + validate-report: Don't leak GError + +2017-05-05 14:57:56 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: Don't end up with invalid action name + When replacing an action structure, also update the action name with + the (new) name from the new structure. Otherwise we end up with + a bogus name from the previous (deleted) structure. + +2017-05-03 17:57:05 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: Avoid invalid memory access + The name of the action comes directly (i.e. not copied) from the + contained GstStructure field. Therefore make sure to take that + name from the proper structure field (copied just before) and + not from an outside one. + +2017-05-04 14:45:32 -0700 Reynaldo H. Verdejo Pinochet + + * validate/tools/gst-validate-images-check.c: + validate: drop superfluous whitespace from cmd description + +2017-05-01 13:51:48 -0700 Scott D Phillips + + * validate/tools/gst-validate-images-check.c: + validate: remove const from outfolder + GOptionEntry's arg_data is of type gpointer which differs in + constness from const gchar*, so remove constness from outfolder. + This fixes a build issue with msvc. + https://bugzilla.gnome.org/show_bug.cgi?id=782031 + +2017-05-04 18:59:42 +0300 Sebastian Dröge + + * meson.build: + Back to development + +2017-05-04 18:59:14 +0300 Sebastian Dröge + + * validate/configure.ac: + Back to development + +=== release 1.12.0 === + +2017-05-04 15:48:44 +0300 Sebastian Dröge + + * validate/ChangeLog: + * validate/NEWS: + * validate/configure.ac: + * validate/gst-validate.doap: + Release 1.12.0 + +2017-04-30 14:35:29 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + launcher: Allow using the base launcher as a test manager + No reason to force people to subclass it in simple cases. + +2017-04-27 16:45:00 -0300 Thibault Saunier + + * validate/tools/gst-validate.c: + tools: Fix dotfile name + +=== release 1.11.91 === + +2017-04-27 17:54:05 +0300 Sebastian Dröge + + * meson.build: + * validate/ChangeLog: + * validate/NEWS: + * validate/configure.ac: + * validate/gst-validate.doap: + Release 1.11.91 + +2015-08-16 13:32:04 +0200 Thibault Saunier + + * validate/data/scenarios/update_stop.scenario: + validate: Fix update_stop.scenario seek property naming + +2017-04-20 10:02:58 -0300 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + launcher:check: Ensure to set build dir before rebuilding + +2017-04-11 07:48:21 +0200 Edward Hervey + + * validate/launcher/baseclasses.py: + baseclasses: Add method to set a specific list of scenarios + https://bugzilla.gnome.org/show_bug.cgi?id=781314 + +2017-04-07 13:49:32 -0700 Reynaldo H. Verdejo Pinochet + + * validate/gst-libs/gst/video/gstvalidatessim.c: + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-override.c: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/plugins/ssim/gstvalidatessim.c: + validate: drop unneeded break+concat in some string literals + Perform other related improvements while at it. + +2017-04-12 11:07:44 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Do not expect track switch to be synchronous for playbin3 + And let following actions to be executed (setting the action as + INTERLACED) which will make sure the track switch happened at some + point. It means the user has to set the pipeline to PLAYING so we can + make it works but we do not have choice here I think + https://bugzilla.gnome.org/show_bug.cgi?id=781213 + +2017-04-10 17:06:25 +0300 Sebastian Dröge + + * meson.build: + Update meson.build version to 1.11.90 too + +2017-04-10 11:43:28 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + validate-pad-monitor: Fix previous commit + Post-send handling was only meant for seek events + +2017-04-10 07:58:01 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: Safely store expected seek values + Protect the expected seek values with the same lock as the one + that will be used to read/validate the resulting segments and flush + values. + Avoids races with duplicated seeks (i.e. a seek that was already + sent and handled via another pad, such as in demuxers). + https://bugzilla.gnome.org/show_bug.cgi?id=781112 + +=== release 1.11.90 === + +2017-04-07 16:36:04 +0300 Sebastian Dröge + + * validate/ChangeLog: + * validate/NEWS: + * validate/configure.ac: + * validate/gst-validate.doap: + Release 1.11.90 + +2017-04-05 15:26:31 -0700 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-scenario.c: + validate: scenario: fix error message on scenario parsing failure + +2017-04-05 14:40:12 -0700 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-report.c: + validate: report: avoid pointless var & associated dup/free + Just pass the replacement string literal to _regex_replace(). + +2017-04-05 14:09:53 -0700 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-report.c: + validate: report: avoid _strdup_printf() if not needed + g_strdup() is enough for this string literal. + +2017-03-31 15:18:09 -0700 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-report.c: + validate: report: fix description for timestamp out of range + Additionally: Fix nits in the descriptions for + SCENARIO_ACTION_EXECUTION_ISSUE and CONFIG_ACTION_TYPE + +2017-04-04 14:52:17 +0200 Edward Hervey + + * validate/launcher/apps/gstvalidate.py: + validate: Improve video fakesink properties + Try to emulate a bit better a real video sink by making the video + fakesink handle/calculate/report QoS. + Also use the same lateness value as default videosink + +2017-03-28 14:35:38 -0700 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-scenario.c: + validate: scenario: improve description blurb for execute-on-idle + Additionally: Fix issues in _register_action_type() documentation. + +2017-03-28 14:01:16 -0700 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-scenario.c: + validate: scenario: fix msg on _lookup_feature() failure + +2017-03-28 13:30:04 -0700 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate: scenario: fix documentation for GstValidateAction + Additionally: + Improve GstValidatePrepareAction documentation + Correct one-off use of 'eos' instead of EOS. + +2017-03-28 12:51:12 -0700 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-scenario.c: + validate: scenario: fix type field in description parameter + Nothing wrong with Sting though :) + Additionally: Fix typo in need-clock-sync's description + +2017-03-21 16:22:50 -0700 Reynaldo H. Verdejo Pinochet + + * validate/gst-libs/gst/video/gssim.c: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/media-descriptor.c: + validate: do not check for NULL before g_free() + g_free() is NULL-safe. + +2017-03-20 15:39:24 -0700 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/media-descriptor-parser.c: + * validate/gst/validate/media-descriptor-writer.c: + * validate/gst/validate/media-descriptor.c: + validate: fix issues with a couple of string constants + Fix missing and/or dupplicated separators, bogus breaks, typos, etc. + +2017-03-20 14:36:36 -0700 Reynaldo H. Verdejo Pinochet + + * validate/tools/gst-validate-images-check.c: + * validate/tools/gst-validate-transcoding.c: + validate: fix whitespace separators in multi-line string constants + Drop dupplicated & add missing ones + Additionally: typo fixes + +2017-03-04 11:13:33 -0500 Nicolas Dufresne + + * validate/gst/validate/Makefile.am: + * validate/gst/validate/meson.build: + * validate/plugins/fault_injection/socket_interposer.c: + * validate/plugins/gapplication/gstvalidategapplication.c: + * validate/plugins/gtk/gstvalidategtk.c: + * validate/plugins/ssim/gstvalidatessim.c: + Rename plugin filenames to match plugin names + - libgstvalidateplugin.so -> libgstvalidatetracer.so + - faultinjection -> validatefaultinjection + - gstvalidategtk -> validategtk + - ssim -> validatessim + https://bugzilla.gnome.org/show_bug.cgi?id=779344 + +2017-03-03 21:31:02 +0100 Stefan Sauer + + * tracer/gsttr-tsplot.py: + tracer: tsplot: tune the plot style a bit + Tweak the styles and spacing. Make the title multi-line and add more info. + +2017-03-01 15:06:59 -0800 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-report.c: + validate: report: avoid unnecessary calls to _append_printf + Additionally: simplify %format magic used for padding + +2017-03-01 14:04:24 -0800 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-report.c: + validate: report: clarify misleading message on skipped actions + Intention is to notify that not all actions were + executed but previous message suggested none were. + Additionally: fix problems in two others + +2017-03-01 13:49:01 -0800 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-report.c: + validate: report: fix _issue_new()'s parameter description + +2017-03-02 21:27:05 +0100 Stefan Sauer + + * tracer/gsttr-tsplot.py: + tracer: tsplot: ensure multiplots use same xrange + Only this way one can visually align events with buffer graphs. + +2017-02-15 17:53:05 +0100 Stefan Sauer + + * tracer/gsttr-tsplot.py: + tracer: tsplot: add a 3rd plot showing cycle vs. durations + This is helpful to spot time-segments where we processes slower than required + for real-time playback. + +2017-03-02 17:35:22 +0100 Edward Hervey + + * validate/launcher/RangeHTTPServer.py: + validate: Make the HTTP server multi-threaded + Avoids having one test blocking all other tests + +2017-02-27 12:10:49 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Add information on media info files parsing failures + +2017-02-27 12:10:16 -0300 Thibault Saunier + + * validate/gst/validate/media-descriptor.c: + validate: Ignore more parser related fields when comparing media caps + +2017-02-20 12:52:06 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate: launcher: Fix the way we retrieve command name + We are now using a list of args for subprocess so just using it is simple now + +2017-02-20 12:51:26 -0300 Thibault Saunier + + * meson.build: + * validate/launcher/meson.build: + validate:launcher: Fix typo in meson build definitions + +2017-02-17 16:29:15 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate: Fix call to decode() on a string + +2017-02-24 14:40:25 -0800 Reynaldo H. Verdejo Pinochet + + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: fix mention to nonexistent option + +2017-02-24 11:29:31 -0800 Reynaldo H. Verdejo Pinochet + + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: improve set-config option description + +2017-02-24 17:32:16 -0300 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + validate:launcher: Fix setting meson build dir + +2017-02-24 16:00:19 +0200 Sebastian Dröge + + * meson.build: + meson: Update version + +2017-02-24 15:37:52 +0200 Sebastian Dröge + + * validate/configure.ac: + Back to development + +=== release 1.11.2 === + +2017-02-24 15:10:12 +0200 Sebastian Dröge + + * validate/ChangeLog: + * validate/NEWS: + * validate/configure.ac: + * validate/gst-validate.doap: + Release 1.11.2 + +2017-02-21 13:39:37 -0300 Thibault Saunier + + * validate/launcher/apps/meson.build: + * validate/launcher/baseclasses.py: + * validate/tools/meson.build: + validate:launcher: Various fixes to make the Test class directly usable + And the launcher installed with meson usable + +2017-02-21 13:38:16 -0300 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + validate: launcher: Do not force using current module dir to run check tests + +2017-02-17 13:37:06 -0800 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-reporter.h: + * validate/gst/validate/gst-validate-scenario.c: + * validate/plugins/gtk/gstvalidategtk.c: + * validate/plugins/ssim/gstvalidatessim.c: + validate: fix multiple occurrences of 'parametter' + +2017-02-17 13:23:34 -0800 Reynaldo H. Verdejo Pinochet + + * validate/tools/gst-validate.c: + validate: fix unbalanced quotation mark in set-subtitle description + + Remove pointless split in string literal + +2017-02-16 15:12:44 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Take ValidateAction execution as a marker of test update + +2017-02-16 14:52:15 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/launcher/baseclasses.py: + validate:scenario: Report action done with the execution duration + +2017-02-16 13:46:06 -0800 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-runner.c: + validate: runner: fix error msg for duplicated runner + - Add trailing whitespace to avoid accidental concatenation + - Fix bunch of typos and some grammar while at it + +2017-02-16 13:30:06 -0800 Reynaldo H. Verdejo Pinochet + + * validate/tools/gst-validate.c: + validate: fix message on runner errors + - Add newline char to avoid accidental concatenation with + actual error message + - Fix grammar while at it + +2017-02-15 18:31:38 -0300 Thibault Saunier + + * validate/gst/validate/meson.build: + meson: Fix Gir *_prefix value to be the sames as with autotools + We want to be able to do GstValidate.Monitor and not + GstValidate.ValidateMonitor. + And do not pass header to the list of sources to build libraries as + it is not needed. + +2017-02-15 18:00:00 -0300 Thibault Saunier + + * validate/gst/validate/meson.build: + meson: Do not forget to install headers + +2017-02-15 00:48:26 +0000 Tim-Philipp Müller + + * validate/pkgconfig/gst-validate-uninstalled.pc.in: + validate: pkgconfig: fix libtool-ism in uninstalled .pc file + +2017-02-15 13:46:03 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Do not switch relative track when no track of type avalaible + This case was not handled and leaded to a division by zero. + +2017-02-15 12:39:18 -0300 Thibault Saunier + + * meson_options.txt: + * validate/meson.build: + validate: meson: Add a way to disable documentation generation + +2017-02-10 15:54:05 -0300 Thibault Saunier + + * validate/tools/meson.build: + validate:meson: Build gst-validate-image-check if possible + +2017-02-08 17:46:23 -0300 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + * validate/launcher/baseclasses.py: + * validate/launcher/testsuites/check.py: + validate:launcher: Add a way to specify a set of tests to run under the leak tracer + https://bugzilla.gnome.org/show_bug.cgi?id=767856 + +2017-02-10 18:15:15 +0100 Stefan Sauer + + * tracer/gsttr-tsplot.py: + tracer: tsplot: separate the event section + Place the events below the buffer-ts. This makes it more readable in many cases. + +2017-02-09 15:52:40 +0100 Stefan Sauer + + * tracer/gsttr-stats.py: + * tracer/gsttr-tsplot.py: + tracer: tools: add a brief tool description + +2017-02-09 15:15:23 +0100 Stefan Sauer + + * tracer/gsttr-tsplot.py: + tracer: tsplot: a new tool to draw buffer-ts vs. clock time graphs + This tool helps to inspect data flow on each pad. It shows buffer timestamps + and events in relation to wall clock. + +2017-02-05 19:37:51 +0100 Stefan Sauer + + * tracer/README: + tracer: REAME: planning update + +2017-02-05 19:37:07 +0100 Stefan Sauer + + * tracer/gsttr-stats.py: + tracer: stats: python style cleanup + +2017-02-07 12:57:06 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-override-registry.c: + valdate: fix element leak in overide-registry + The elements were never released once created. + https://bugzilla.gnome.org/show_bug.cgi?id=778279 + +2017-02-07 12:50:33 +0100 Guillaume Desmottes + + * validate/tests/check/validate/overrides.c: + validate: fix leak in overrides test + The runner was never released. + https://bugzilla.gnome.org/show_bug.cgi?id=778279 + +2017-02-07 12:04:45 +0100 Guillaume Desmottes + + * validate/tests/check/validate/padmonitor.c: + validate: fix leaks in pad monitor test + - monitors were never released + - reports were leaked + - GstValidateMediaDescriptor was leaked + - caps were leaked: gst_check_setup_events_with_stream_id() and + gst_event_new_caps() don't consume the caps + - srcpad were never released + https://bugzilla.gnome.org/show_bug.cgi?id=778279 + +2017-02-07 12:28:02 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-pad-monitor.c: + validate: pad-monitor: fix caps leaks + These caps were not released when disposing the pad monitor. + https://bugzilla.gnome.org/show_bug.cgi?id=778279 + +2017-02-07 13:12:09 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/launcher/baseclasses.py: + * validate/launcher/reporters.py: + * validate/launcher/utils.py: + validate: Mark tests as SKIPPED when installation is missing a GStreamer plugin + +2017-02-06 15:51:57 -0300 Thibault Saunier + + * validate/gst/validate/media-descriptor.c: + validate: Do not fail media check when fields are related to (decoding) elements + In the case of h264 the stream might very well be in `nal` format but the decoder + might not accept it thus the parser converts to `byte-stream`, leading + to a correct stream detection but a failure in the validate-media-check + tool. + +2017-02-06 12:16:41 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + validate:launcher: Add an option for user to modify timeout values + Allowing to expand the test timeout when running on slow platforms + +2017-02-03 11:02:49 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Verify that Gst supression file could be found + +2017-02-02 15:47:30 -0300 Thibault Saunier + + * validate/gst-libs/gst/video/meson.build: + validate:meson: Add pbutils as a dependency on the video library + +2017-02-02 14:29:30 +0100 Guillaume Desmottes + + * validate/tests/launcher_tests/meson.build: + validate: meson: fix --validate-tools-path argument + The path passed to --validate-tools-path was wrong when building using + gst-build, preventing the launcher to find the validate tools. + https://bugzilla.gnome.org/show_bug.cgi?id=777982 + Differential Revision: https://phabricator.freedesktop.org/D1634 + +2017-02-02 08:19:01 -0300 Thibault Saunier + + * validate/plugins/ssim/meson.build: + validate:meson: Add pbutils as a dependency + Fixing build failure https://ci.appveyor.com/project/thiblahute/gst-build-ge9m5/build/1.0.1197 + FAILED: cl @subprojects/gst-devtools/validate/gst-libs/gst/video/gstvalidatevideo@sta/gstvalidatessim.c.obj.rsp + c:\projects\gst-build-ge9m5\subprojects\gst-plugins-base\gst-libs\gst\pbutils\pbutils.h(30): fatal error C1083: Cannot open include file: 'gst/pbutils/pbutils-enumtypes.h': No such file or directory + FAILED: cl @subprojects/gst-devtools/validate/plugins/ssim/gstvalidatessim@sha/gstvalidatessim.c.obj.rsp + c:\projects\gst-build-ge9m5\subprojects\gst-plugins-base\gst-libs\gst\pbutils\pbutils.h(30): fatal error C1083: Cannot open include file: 'gst/pbutils/pbutils-enumtypes.h': No such file or directory + +2017-01-30 22:22:20 +0000 Thibault Saunier + + * validate/gst/validate/validate.c: + validate: Avoid assertion when trying to pass the config as a caps + +2017-01-31 08:24:32 -0300 Thibault Saunier + + * validate/gst-libs/gst/video/meson.build: + validate: Fix build if cairo is not avalaible + +2017-01-30 22:20:11 +0000 Thibault Saunier + + * validate/gst-libs/gst/video/gstvalidatessim.c: + validate:plugins: Handle the case where we have a pipelines with only 1 frame + +2017-01-31 11:35:30 +0100 Guillaume Desmottes + + * validate/tests/check/validate/monitoring.c: + * validate/tests/check/validate/overrides.c: + * validate/tests/check/validate/padmonitor.c: + * validate/tests/check/validate/reporting.c: + validate: tests: call gst_validate_deinit() + gst_validate_deinit() needs to be called when the test is done to remove + false positives when using the leaks tracer. + https://bugzilla.gnome.org/show_bug.cgi?id=777977 + Differential Revision: https://phabricator.freedesktop.org/D1630 + +2017-01-30 19:19:04 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: Allow passing extra env var to simple pipeline generator + +2017-01-30 21:19:00 +0000 Thibault Saunier + + * meson.build: + * validate/gst-libs/gst/meson.build: + * validate/gst-libs/gst/video/meson.build: + * validate/gst-libs/meson.build: + * validate/meson.build: + * validate/plugins/fault_injection/meson.build: + * validate/plugins/gapplication/meson.build: + * validate/plugins/gtk/meson.build: + * validate/plugins/meson.build: + * validate/plugins/ssim/meson.build: + meson:validate: Build validate plugins + +2017-01-24 12:28:15 +0900 Wonchul Lee + + * validate/gst/validate/Makefile.am: + validate: fix linker flags for validate plugin + https://bugzilla.gnome.org/show_bug.cgi?id=777938 + +2017-01-25 21:41:31 +0000 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + validate: Handle the case where QUERY has an EMPTY caps filter + In our algorithm describing caps negotiation issues. + +2017-01-10 12:32:31 -0300 Thibault Saunier + + * validate/gst/validate/media-descriptor.c: + validate: Stop comparing buffers offset values in media descriptor + This value can vary for some external reasons and should not matter + for now. + +2017-01-13 12:39:29 +0000 Tim-Philipp Müller + + * meson.build: + meson: bump version + +2017-01-12 16:33:03 +0200 Sebastian Dröge + + * validate/configure.ac: + Back to development + +=== release 1.11.1 === + +2017-01-12 16:28:02 +0200 Sebastian Dröge + + * validate/ChangeLog: + * validate/NEWS: + * validate/configure.ac: + * validate/gst-validate.doap: + Release 1.11.1 + +2017-01-06 13:14:17 -0300 Thibault Saunier + + * meson.build: + validate: Fix setting of the testsuite version + If nano is set, it means we are running in git/unreleased version + +2017-01-06 12:09:13 -0300 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + * validate/launcher/baseclasses.py: + validate: Make sure to update children environment from current env + And minor fix in function call + +2017-01-06 11:48:01 -0300 Thibault Saunier + + * validate/launcher/apps/gstcheck.py: + validate: check: Enhance test names + +2017-01-04 17:40:59 +0100 Guillaume Desmottes + + * validate/pkgconfig/Makefile.am: + * validate/pkgconfig/gst-validate-uninstalled.pc.in: + * validate/pkgconfig/meson.build: + meson: generate pkg-config -uninstalled pc files + Generating those files is useful for users building the GStreamer stack + using meson and having to link it to another project which is still + using the autotools. + Fixed the -uninstalled pc file libdir path while I was on it. + https://bugzilla.gnome.org/show_bug.cgi?id=776810 + +2017-01-03 16:25:47 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Make the IPC server socket blocking + 0.0 does not mean blocking in python3 and makes everything failling + on windows. + +2017-01-03 15:58:35 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/utils.py: + validate: Properly kill subprocesses on windows + +2017-01-03 15:34:39 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/config.py.in: + * validate/tools/gst-validate-launcher.in: + validate:launcher: Fix running on windows + +2017-01-03 14:52:38 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate: Respect active testers when listing tests + +2017-01-03 14:38:24 -0300 Thibault Saunier + + * validate/launcher/__init__.py: + * validate/tools/gst-validate-launcher.in: + validate: Make validate launcher apps work in a meson uninstalled env + +2017-01-03 13:11:42 -0300 Thibault Saunier + + * validate/launcher/apps/Makefile.am: + * validate/launcher/apps/gstcheck.py: + validate: Fix gstcheck when not running on a meson build dir + +2017-01-03 13:01:31 -0300 Thibault Saunier + + * validate/configure.ac: + * validate/launcher/Makefile.am: + * validate/launcher/apps/gstcheck.py: + * validate/launcher/baseclasses.py: + * validate/launcher/config.py.in: + * validate/launcher/main.py: + * validate/launcher/meson.build: + * validate/launcher/testsuites/Makefile.am: + * validate/launcher/testsuites/check.py: + * validate/launcher/testsuites/meson.build: + Revert "Revert "validate:launcher: Add an app handler for unit tests described in meson"" + This reverts commit 5656e2a1b208155be36cf16b08a9ffcc95694328. + +2017-01-03 07:05:12 +0100 Edward Hervey + + * validate/launcher/main.py: + validate: Ensure non-standard testsuite location is taken into account + This is a regression that was introduced by 6504b9152cfd2d78ee3d773a0a32eec1900f955c + If we have non-standard main_dir or qa_assets, make sure we prepend the + checked-out testsuites directory to the list of expected ones + +2017-01-02 16:11:22 +0100 Edward Hervey + + * validate/configure.ac: + * validate/launcher/Makefile.am: + * validate/launcher/apps/gstcheck.py: + * validate/launcher/baseclasses.py: + * validate/launcher/config.py.in: + * validate/launcher/main.py: + * validate/launcher/meson.build: + * validate/launcher/testsuites/Makefile.am: + * validate/launcher/testsuites/check.py: + * validate/launcher/testsuites/meson.build: + Revert "validate:launcher: Add an app handler for unit tests described in meson" + This reverts commit a4aa5c60bb2015f006103317800fb1fddb5718e6. + Does not work outside of gst-build (i.e. validate won't work when used either + installed, in a prefix, or in gst-uninstalled). + +2016-12-30 11:53:13 +0100 christophecvr + + * mediainfo/autogen.sh: + * mediainfo/configure.ac: + * mediainfo/src/Makefile.am: + mediainfo: update build setup + Fix intltool build setup to make it build on ubuntu 16.04 as well. + Fixes: https://bugzilla.gnome.org/show_bug.cgi?id=776523 + +2016-12-30 11:43:01 +0100 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mediinfo: use less deprecated gtk api + +2016-12-30 11:41:10 +0100 Stefan Sauer + + * mediainfo/configure.ac: + mediainfo: use libgee-0.8 + We were using libgee-0.6 which was using gee-1.0 in pkgconfig. + https://bugzilla.gnome.org/show_bug.cgi?id=776523 + +2016-12-28 20:29:50 +0100 Stefan Sauer + + * mediainfo/src/mi-app.vala: + * mediainfo/src/mi-info.vala: + mediinfo: replace some deprecated UI by the new one + +2016-12-28 20:29:23 +0100 Stefan Sauer + + * mediainfo/vapi/config.vapi: + mediinfo: remove unused field + +2016-12-28 20:27:58 +0100 Stefan Sauer + + * mediainfo/src/Makefile.am: + mediainfo: remove 0.10 libs + We check those in configure anyway. + +2016-12-25 11:34:33 +0100 Stefan Sauer + + * tracer/tracer/analysis_runner.py: + * tracer/tracer/parser.py: + * tracer/tracer/parser_perf.py: + * tracer/tracer/parser_test.py: + tracer: parser: small speedup + Add a parser_perf test. Skip the extra filter stage and change the regex to + match on category=TRACE lines only. + Also flip the check in analysis_runner, since we only have a few tracer + classes in the beginning, the rest are tracer entries. + +2016-12-22 16:23:02 +0100 Stefan Sauer + + * tracer/gsttr-stats.py: + tracer: gsttr-stats: add a fast path for tracer-entry matching + Extract the structure name from the string and only parse the full structure, + if we are going to handle it. + +2016-12-23 15:00:53 -0300 Thibault Saunier + + * validate/tools/gst-validate-transcoding.c: + validate-transcoding: Use standard GstEncodingProfile deserialization function + +2016-12-23 14:58:56 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Take into account test duration when filtering them + Otherwise running -t 'some.*test' will run long tests (longer than hard + timeout) which is not what the user expect. + +2016-12-22 19:24:22 +0200 Sebastian Dröge + + * validate/gst/validate/gst-validate-runner.c: + validate-runner: Use correct enum in return value + gst-validate-runner.c:856:7: error: implicit conversion from enumeration type 'GstValidateReportLevel' to different enumeration type 'GstValidateReportingDetails' [-Werror,-Wenum-conversion] + GST_VALIDATE_REPORT_LEVEL_UNKNOWN); + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +2016-12-22 10:08:32 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-runner.c: + validate: Add safe guards to runner API + +2016-12-22 10:08:31 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate: Whitlist blacklisted test only when explicitely + Otherwise running -t '.*reverse.*' will also run blacklisted + tests which is probably not what use wants. + +2016-12-22 10:08:30 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + launcher: Make --update-media-info check if generating frames info or not + +2016-12-22 10:08:28 -0300 Thibault Saunier + + * validate/configure.ac: + * validate/launcher/Makefile.am: + * validate/launcher/apps/gstcheck.py: + * validate/launcher/baseclasses.py: + * validate/launcher/config.py.in: + * validate/launcher/main.py: + * validate/launcher/meson.build: + * validate/launcher/testsuites/Makefile.am: + * validate/launcher/testsuites/check.py: + * validate/launcher/testsuites/meson.build: + validate:launcher: Add an app handler for unit tests described in meson + This way we can run all tests with the launcher which brings in many + features. + And add a testsuite for GStreamer unit tests. + +2016-12-22 10:08:27 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Allow specifying a timeout factor + +2016-12-22 10:08:26 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Print the iteration number when running forever + +2016-12-22 10:08:25 -0300 Thibault Saunier + + * validate/launcher/main.py: + validate:launcher: Add a -v option to print subprocesses to stdout + +2016-12-22 10:08:24 -0300 Thibault Saunier + + * validate/launcher/main.py: + validate:launcher: Run cpu_count test in parallel by default + +2016-12-22 10:08:23 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + validate:launcher: Do not list tests on unneeded testers + +2016-12-22 10:08:21 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + validate:launcher: Allow specifying several testsuite dirs + +2016-12-22 10:07:58 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + validate:launcher: Stop running test subprocesses in a shell + And instead properly use a list of argument for the subprocesses. + +2016-12-20 21:00:58 +0100 Stefan Sauer + + * tracer/gsttr-stats.py: + tracer: gsttr-stats: don't average aggregated values + Only collect the first/last values for them. + +2016-12-20 12:10:43 +0100 Stefan Sauer + + * tracer/gsttr-stats.py: + tracer/gsttr-stats: skip optional fields + +2016-12-20 12:09:21 +0100 Stefan Sauer + + * tracer/tracer/structure.py: + * tracer/tracer/structure_test.py: + tracer/structure: handle boolean fields + +2016-12-20 10:27:45 +0100 Stefan Sauer + + * tracer/gsttr-stats.py: + tracer/gsttr-stats: improve formatting + Add a headline and print the results as columns. Use ':' in ts format. + +2016-12-20 10:26:55 +0100 Stefan Sauer + + * tracer/README: + tracer/README: update docs + Update status of what is done and what we want to figure still. + +2016-12-20 09:25:30 +0100 Stefan Sauer + + * tracer/tracer/parser.py: + tracer/parser: use local vars in the iterator function + This saves variable lookups in this thight loop. + +2016-12-20 08:24:32 +0100 Stefan Sauer + + * tracer/tracer/analysis_runner.py: + * tracer/tracer/analysis_runner_test.py: + * tracer/tracer/analyzer.py: + * tracer/tracer/parser.py: + * tracer/tracer/parser_test.py: + * tracer/tracer/structure.py: + * tracer/tracer/structure_perf.py: + * tracer/tracer/structure_test.py: + tracer: pep8 cleanup + +2016-12-20 08:17:34 +0100 Stefan Sauer + + * tracer/tracer/analysis_runner.py: + * tracer/tracer/analyzer.py: + * tracer/tracer/parser.py: + * tracer/tracer/structure.py: + tracer: update docstrings + +2016-12-19 22:37:20 +0100 Stefan Sauer + + * tracer/tracer/structure.py: + tracer/structure: improve performance + Use local vars in the parser. This way we can make them static methods. + +2016-12-16 15:00:04 +0100 Stefan Sauer + + * tracer/gsttr-stats.py: + * tracer/tracer/analysis_runner.py: + tracer/gsttr-stats: adding some filtering options + Also adding a way to show what is in the file. + +2016-12-16 14:08:46 +0100 Stefan Sauer + + * tracer/Makefile: + * tracer/tracer/parser_test.py: + tracer/Makefile: fix test invocation + And fix a deprecation warning. + +2016-12-16 14:07:45 +0100 Stefan Sauer + + * tracer/tracer/structure.py: + * tracer/tracer/structure_perf.py: + * tracer/tracer/structure_test.py: + tracer/structure: add more tests and a benchmark + +2016-12-14 21:28:12 +0100 Stefan Sauer + + * tracer/gsttr-stats.py: + tracer/gsttr-stats: move time unit hack into method + +2016-12-14 19:07:22 +0100 Stefan Sauer + + * tracer/gsttr-stats.py: + * tracer/tracer/analysis_runner.py: + * tracer/tracer/analysis_runner_test.py: + * tracer/tracer/analyzer.py: + tracer/gsttr-stats: split Analyzer into Analyzer and AnalysisRunner + This lets us run chain analyzers. Move the stats collection into the + gsttr-stats tool. + +2016-12-12 22:41:23 +0100 Stefan Sauer + + * tracer/gsttr-stats.py: + tracer: add a first tool to report aggregated findings + A tool to report min/max/avg values per scope and traced value. + +2016-12-12 22:38:57 +0100 Stefan Sauer + + * tracer/Makefile: + * tracer/README: + * tracer/tracer/analyzer.py: + * tracer/tracer/analyzer_test.py: + * tracer/tracer/parser.py: + * tracer/tracer/parser_test.py: + * tracer/tracer/structure.py: + * tracer/tracer/structure_test.py: + tracer: add new python library to process tracer logs + This is the beginning of a python library for wrting tools that process tracer + logs. This library contains a structure parser written in python to avoid the + dependency on gobject introspection (and the slowness and non pythoness that + comes with it). + +2016-12-13 13:26:35 +0100 Stefan Sauer + + * debug-viewer/GstDebugViewer/Data.py: + debug-viewer: inline expression + +2016-12-19 16:17:56 +0100 Edward Hervey + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: Properly get the rate for action validation + This issue was most likely introduced by the refactoring of the + position querying into a standalone function. + In execute_next_action() the rate variable was never replaced by + the current rate of the pipeline, this would result in all reverse + playback actions to trigger immediately instead of waiting for + the actual target time. + https://bugzilla.gnome.org/show_bug.cgi?id=776280 + +2016-12-12 15:07:30 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + validate: Fix usage of get_stack)trace after API change + +2016-12-12 12:27:42 -0300 Thibault Saunier + + * validate/tools/gst-validate-transcoding.c: + validate: transcode: No buffering handling when the sink is not synced on the clock + It makes no sense to pause the pipeline and wait for buffering to be + done when the pipeline is just processing the data as it comes + in without synchronizing on the clock. + +2016-12-09 17:43:53 -0300 Thibault Saunier + + * meson.build: + meson: Support building without Gst debug + +2016-12-07 15:11:33 -0800 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + validate: fix small grammar nit + +2016-12-01 10:51:56 -0300 Thibault Saunier + + * validate/launcher/reporters.py: + launcher: Handle stack trace information as jenkins expect it in the xunit file + +2016-11-30 14:07:04 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Handle missing media info file + +2016-11-30 13:32:09 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/tests/launcher_tests/test_validate.py: + validate: Properly handle proxy pads generating detailed info about NNEs + In cases where we had a two consecutive bins with proxy pads, we could + segfault because we were dereferencing a NULL pointer to pad. + +2016-11-18 14:45:42 -0300 Thibault Saunier + + * validate/tests/check/meson.build: + * validate/tests/getpluginsdir: + * validate/tests/launcher_tests/meson.build: + * validate/tests/meson.build: + * validate/tools/meson.build: + meson: Modernize the way we set test env variables + Removing the now useless getplugindirs script + +2016-11-30 07:40:05 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Make sure that the IPC server is shutdown before closing the socket + +2016-11-29 14:47:35 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Make sure to check string when verifying expected failures + The value can potentially be None and we should handle that + +2016-11-26 10:25:43 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/launcher/baseclasses.py: + * validate/launcher/utils.py: + validate:launcher: Allow specifying timeout as a known issue + And minor cleanups + +2016-11-26 10:24:11 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/reporters.py: + validate:launcher: Properly report stack trace as such in the xunit file + +2016-11-26 09:27:45 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Remove now useless validatelog + We are now doing IPC to communicate with the launcher + so let it simply go to stdout. + +2016-11-24 10:29:53 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/utils.py: + validate:launcher: Handle checking bug status for expected failures + +2016-11-26 11:26:05 +0000 Tim-Philipp Müller + + * .gitmodules: + common: use https protocol for common submodule + https://bugzilla.gnome.org/show_bug.cgi?id=775110 + +2016-11-23 08:38:49 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-monitor-factory.c: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate: Fix GI warnings + +2016-11-19 12:36:32 +0200 Sebastian Dröge + + * meson.build: + * validate/meson.build: + meson: Move vs_module_defs_dir to the validate subdirectory + It's validate/win32/ and not just win32/ + https://bugzilla.gnome.org/show_bug.cgi?id=774638 + +2016-11-18 10:06:14 -0800 Scott D Phillips + + * validate/Makefile.am: + * validate/win32/MANIFEST: + * validate/win32/common/libgstvalidate.def: + validate: make: include common/win32.mak + With the addition of the .def file for validate we need to make + sure the check-export script from common gets executed so that the + .def stays up to date. + https://bugzilla.gnome.org/show_bug.cgi?id=774638 + +2016-11-17 17:26:49 -0800 Scott D Phillips + + * meson.build: + meson: Add ignored warnings for MSVC + https://bugzilla.gnome.org/show_bug.cgi?id=774656 + +2016-11-17 10:00:25 -0800 Scott D Phillips + + * meson.build: + * validate/gst/validate/meson.build: + * win32/common/libgstvalidate.def: + Enable building with MSVC + https://bugzilla.gnome.org/show_bug.cgi?id=774638 + +2016-11-17 10:28:01 -0800 Scott D Phillips + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: fix return type of get_range_func + The return type of GstPadGetRangeFunction is GstFlowReturn + https://bugzilla.gnome.org/show_bug.cgi?id=774638 + +2016-11-17 10:25:37 -0800 Scott D Phillips + + * validate/gst/validate/validate.c: + validate: Remove #include + It isn't needed and isn't present in non-posix environments like windows + with MSVC or mingw. + https://bugzilla.gnome.org/show_bug.cgi?id=774638 + +2016-11-17 10:24:08 -0800 Scott D Phillips + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/tools/gst-validate-transcoding.c: + Fix MSVC const warnings + https://bugzilla.gnome.org/show_bug.cgi?id=774638 + +2016-11-17 10:19:22 -0800 Scott D Phillips + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/gst/validate/gst-validate-scenario.c: + Pass gint/guint pointers instead of enum pointers + The underlying integer type for enums are implementation defined and may + not be the same size as gint/guint. So implicitly casting from pointers- + to-enum-types to pointers-to-int-types is unsafe. MSVC warns on these. + https://bugzilla.gnome.org/show_bug.cgi?id=774638 + +2016-11-17 15:43:15 -0300 Thibault Saunier + + * validate/launcher/main.py: + validate: Minor documentation fixes. + +2016-11-16 10:47:21 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + * validate/launcher/utils.py: + validate: launcher: Allow checking if bugs linked to blacklist is fixed + +2016-11-16 10:48:26 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Consider wanted tests as whitelisted + +2016-11-15 18:55:09 -0300 Thibault Saunier + + * validate/gst/validate/meson.build: + validate: meson: Do not mixup gstvalidate lib and tracer plugin + Even though it is mostly the same thing in the end + +2016-11-14 18:41:34 -0300 Thibault Saunier + + * validate/launcher/utils.py: + validate:launcher: Timeout if running gdb takes too much time + +2016-10-29 11:22:31 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + validate:launcher: Allow running the testsuite N number of times + +2016-11-14 13:05:04 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/media-descriptor-writer.c: + validate: Remove extra buffering status prints + +2016-11-09 17:37:24 -0300 Thibault Saunier + + * validate/launcher/utils.py: + validate:launcher: Fix usage in an uninstalled environment + +2016-11-08 18:06:19 -0300 Thibault Saunier + + * validate/config.h.meson: + * validate/configure.ac: + * validate/gst/validate/Makefile.am: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/meson.build: + * validate/meson.build: + validate: Use gst_debug_get_stack_trace instead of our implementation + And remove now useless config.h.meson file + +2016-11-07 17:20:09 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/utils.py: + validate:launcher: Try to generate a backtrace on segfaults + +2016-11-04 18:04:37 -0300 Thibault Saunier + + * hooks/pre-commit-python.hook: + * validate/launcher/RangeHTTPServer.py: + * validate/launcher/__init__.py: + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + * validate/launcher/config.py.in: + * validate/launcher/httpserver.py: + * validate/launcher/loggable.py: + * validate/launcher/main.py: + * validate/launcher/reporters.py: + * validate/launcher/utils.py: + * validate/launcher/vfb_server.py: + * validate/tools/gst-validate-analyze: + * validate/tools/gst-validate-launcher.in: + validate:launcher: Port to Python3 + And sync logging.py with Pitivi version + +2016-11-04 14:45:19 -0300 Thibault Saunier + + * meson.build: + meson: Unset the plugin paths to generate the .gir files + Avoiding problems when using subproject: + 'Failed to load plugin something.so file too short' + +2016-11-03 16:17:08 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + validate: report: Do not unref an object where we do not own a ref + g_io_stream_get_output_stream is transfer none + +2016-11-03 11:22:08 -0300 Thibault Saunier + + * validate/gst/validate/Makefile.am: + * validate/gst/validate/meson.build: + validate: gir: We should not depend on GstVideo + We do not depend on it at all in GstValidate itself + https://bugzilla.gnome.org/show_bug.cgi?id=773898 + +2016-11-02 08:28:27 -0300 Thibault Saunier + + * meson.build: + * validate/meson.build: + validate: Allow using json-glib as a subproject + +2016-11-02 17:25:21 -0300 Thibault Saunier + + * validate/tools/gst-validate-media-check.c: + validate: media check: Always print runner infos when bailing out. + +2016-11-02 12:43:54 -0300 Thibault Saunier + + * validate/launcher/vfb_server.py: + validate:launcher: Do not try to set DISPLAY envvar to None + +2016-11-01 18:11:13 +0000 Tim-Philipp Müller + + * meson.build: + meson: update version + +=== release 1.11.0 === + +2016-11-01 18:53:16 +0200 Sebastian Dröge + + * validate/configure.ac: + Back to development + +=== release 1.10.0 === + +2016-11-01 18:16:13 +0200 Sebastian Dröge + + * validate/ChangeLog: + * validate/NEWS: + * validate/configure.ac: + * validate/gst-validate.doap: + Release 1.10.0 + +2016-10-26 17:58:58 +0200 Edward Hervey + + * validate/data/scenarios/Makefile.am: + scenarios: Simplify Makefile + +2016-10-26 17:51:37 +0200 Edward Hervey + + * validate/data/scenarios/Makefile.am: + * validate/data/scenarios/trick_mode_seeks.scenario: + scenarios: Add a forward key-unit trick mode scenario + Not enabled for the time being + +2016-10-26 17:34:49 +0200 Edward Hervey + + * validate/launcher/baseclasses.py: + baseclasses: Also check for minimum number of video-tracks + Some scenarios might only be for video files and are meaningless for + audio-only files + +2016-10-25 08:53:59 -0700 Scott D Phillips + + * meson.build: + meson: Don't depend on gstreamer-check-1.0 on windows + https://bugzilla.gnome.org/show_bug.cgi?id=773114 + +2016-10-25 10:52:12 +0530 Nirbheek Chauhan + + * meson.build: + * validate/tests/check/meson.build: + Revert "meson: move gstreamer-check-1.0 dependency to validate/tests/check" + This reverts commit e8e51bdad499b38d2acc0216dc124bb82b0bd72b. + Does not actually work. See: + https://bugzilla.gnome.org/show_bug.cgi?id=773114#c31 + +2016-10-21 00:48:47 -0700 Scott D Phillips + + * meson.build: + * validate/tests/check/meson.build: + meson: move gstreamer-check-1.0 dependency to validate/tests/check + https://bugzilla.gnome.org/show_bug.cgi?id=773114 + +2016-10-18 15:41:11 +0200 Edward Hervey + + * validate/launcher/apps/gstvalidate.py: + validate: Blacklist failing hls tests + See https://bugzilla.gnome.org/show_bug.cgi?id=773159 + +2016-10-14 11:05:26 -0400 Nicolas Dufresne + + * validate/gst/validate/Makefile.am: + Also fix link issue in the plugin with libdw + +2016-10-14 10:52:53 -0400 Nicolas Dufresne + + * validate/gst/validate/gst-validate-report.c: + Fix non-C89 code + In the automake build system we force C89 which does not allow mixing + code and declaration. + +2016-10-14 10:48:16 -0400 Nicolas Dufresne + + * validate/gst/validate/Makefile.am: + Fix missing linker flags for libdw + This was added in the meson build but was only checked and not used in + the automake build. + +2016-10-10 15:59:49 +0200 Stefan Sauer + + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + debug-viewer: window: add helper to get visible range + Move this code to the window class, as multiple plugins are going to need it. + +2016-10-09 12:55:59 +0200 Stefan Sauer + + * debug-viewer/GstDebugViewer/GUI/models.py: + degbug-viewer: models: only temporarilly modify the row for filtering + This avoid that we have to chek the type in the getter. + Also update the comment - we need the strip since the readline call + will not strip the newline. + +2016-06-21 17:56:58 +0100 William Manley + + * codecanalyzer/autogen.sh: + gst-devtools/codecanalyser: Fix typo NO_CONFIGURE should be NOCONFIGURE + For consistency with the rest of the autogen.sh scripts. + https://bugzilla.gnome.org/show_bug.cgi?id=772616 + +2016-10-08 22:23:39 +0200 Stefan Sauer + + * debug-viewer/GstDebugViewer/GUI/app.py: + * debug-viewer/data/main-window.ui: + debug-viewer: app: Switch for rc_parse to css + This gets us the line shading back and some size savings. + +2016-10-08 14:49:38 +0200 Stefan Sauer + + * debug-viewer/GstDebugViewer/GUI/models.py: + debug-viewer: models: allow filter to check COL_MESSAGE + COL_MESSAGE contains the message offset as an internal optimization. When + preparing a row for filters, we need to replace this. Otherwise filters + get an 'int' instead of the 'string' they expect. + +2016-10-07 13:42:02 +0900 Jinwoo Ahn + + * validate/tools/gst-validate.c: + validate: fix typo + https://bugzilla.gnome.org/show_bug.cgi?id=772543 + +2016-10-05 14:50:53 +0900 Jinwoo Ahn + + * validate/gst/validate/gst-validate-report.c: + validate: fix typo in gst-validate-report.c + parametter -> parameter + https://bugzilla.gnome.org/show_bug.cgi?id=772439 + +2016-09-30 22:29:43 +0200 Stefan Sauer + + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + debug-viewer: small code cleanups + Inline a few statements. Remove unused variables. + +2016-09-30 22:31:24 +0200 Stefan Sauer + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/Plugins/__init__.py: + debug-viewer: add a few doc strings + +2016-09-30 11:35:41 -0300 Thibault Saunier + + * hooks/multi-pre-commit.hook: + * hooks/pre-commit-python.hook: + * hooks/pre-commit.hook: + * meson.build: + * validate/tests/getpluginsdir: + meson: Setup pre commit hook and fix getpluginsdir for standalone case + +2016-09-30 14:57:27 +0100 Tim-Philipp Müller + + * meson.build: + meson: update version + +=== release 1.9.90 === + +2016-09-30 13:06:16 +0300 Sebastian Dröge + + * validate/ChangeLog: + * validate/NEWS: + * validate/configure.ac: + * validate/gst-validate.doap: + Release 1.9.90 + +2016-09-23 20:40:52 -0300 Thibault Saunier + + * validate/docs/validate/meson.build: + meson: Fix gtkdoc using new meson features + +2016-09-28 21:19:29 +0200 Stefan Sauer + + * debug-viewer/GstDebugViewer/Plugins/ColorizeRows.py: + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + * debug-viewer/GstDebugViewer/Plugins/__init__.py: + debug-viewer: initialize all features from tuples + Also add a first doc string about the plugin initialisation. + +2016-09-28 20:38:55 +0200 Stefan Sauer + + * debug-viewer/GstDebugViewer/Common/Data.py: + * debug-viewer/GstDebugViewer/Common/GUI.py: + * debug-viewer/GstDebugViewer/Common/Main.py: + * debug-viewer/GstDebugViewer/Common/__init__.py: + * debug-viewer/GstDebugViewer/Common/generictreemodel.py: + * debug-viewer/GstDebugViewer/Common/utils.py: + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI/__init__.py: + * debug-viewer/GstDebugViewer/GUI/app.py: + * debug-viewer/GstDebugViewer/GUI/colors.py: + * debug-viewer/GstDebugViewer/GUI/columns.py: + * debug-viewer/GstDebugViewer/GUI/filters.py: + * debug-viewer/GstDebugViewer/GUI/models.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/GstDebugViewer/Main.py: + * debug-viewer/GstDebugViewer/Plugins/ColorizeRows.py: + * debug-viewer/GstDebugViewer/Plugins/FileProperties.py: + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + * debug-viewer/GstDebugViewer/Plugins/__init__.py: + formatting: run autopep8 over all files + We have a commit hook on the repo. Get all files to match the pep8 guidelines. + +2016-09-28 20:34:53 +0200 Stefan Sauer + + * debug-viewer/GstDebugViewer/Plugins/FileProperties.py: + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + * debug-viewer/GstDebugViewer/Plugins/__init__.py: + debug-viewer: cleanup imports in plugins + Don't use * imports. Don't rely on package level imports. + +2016-09-21 16:41:45 -0300 Thibault Saunier + + * validate/launcher/meson.build: + * validate/tools/meson.build: + meson: Fix installing configured files + +2016-09-21 16:14:59 -0300 Thibault Saunier + + * validate/docs/validate/envvariables.xml: + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/validate.c: + validate: Allow doting the pipeline on issue reporting + And let the user configure on what level of issues to do it + by setting the GST_VALIDATE_CONFIG env var. + Always dot on critical issues. + +2016-09-21 16:00:18 -0300 Thibault Saunier + + * validate/docs/validate/envvariables.xml: + * validate/gst/validate/validate.c: + validate: Allow passing a GST_VALIDATE_CONFIG as a string + Instead of forcing user to put it in a file. + We are simply using the GstCaps synthax to parse it. + +2016-09-21 15:48:57 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-monitor.h: + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-reporter.h: + * validate/gst/validate/gst-validate-scenario.c: + validate: Add a reference to the pipeline from each monitor + That will allow us to add more flexibility regarding the way + we report thing to the user and will allow us to properly make + reports per pipeline. + +2016-09-21 14:10:53 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Minor fix in returncode check + +2016-09-21 14:07:29 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-runner.c: + validate: Always print trace for critical issues + And take into account issue details level to generate backtrace. + +2016-09-21 12:26:17 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-runner.c: + * validate/tests/check/validate/reporting.c: + validate: Add support for setting a report level for an issue type + Until now we could set report levels to the monitor, this adds support + for setting report level for the issue types too. + +2016-09-25 18:23:30 -0700 Reynaldo H. Verdejo Pinochet + + * validate/tools/gst-validate-transcoding.c: + validate: transcoding: fix several error messages + No encoders found, no static src/sink pads found and keyunit and force-stop + error conditions. + +2016-09-25 18:03:45 -0700 Reynaldo H. Verdejo Pinochet + + * validate/tools/gst-validate.c: + validate: fix grammar on subtitle-file action description + +2016-09-22 15:56:24 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + validate: Dump the infos about NNEs if we can not properly determine the problem + It should anyway be pretty interesting pieces of information. + +2016-09-22 11:37:57 -0400 Javier Martinez Canillas + + * validate/gst/validate/gst-validate-report.c: + validate: fix build warning in generate_unwind_trace() + The unw_word_t type has different sizes for 32-bit and 64-bit, so using the + %lx format specifier on a 32-bit CPU leads to the following compile warning: + CC libgstvalidate_1.0_la-gst-validate-report.lo + gst-validate-report.c: In function 'generate_unwind_trace': + gst-validate-report.c:137:36: error: format '%lx' expects argument of type 'long unsigned int', but argument 4 has type 'unw_word_t {aka unsigned int}' [-Werror=format=] + g_string_append_printf (trace, "%s (0x%lx)\n", name, offset); + Cast to long so the %lx fomart specifier can be always used. + +2016-09-22 12:36:29 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + validate: Avoid checking NULL pads when generating NNE reports + +2016-09-20 13:39:02 +0100 Tim-Philipp Müller + + * validate/gst/validate/gst-validate-report.c: + validate: fix compiler warnings + gst-validate-report.c: In function ‘generate_unwind_trace’: + gst-validate-report.c:116:1: error: old-style function definition [-Werror=old-style-definition] + generate_unwind_trace () + ^~~~~~~~~~~~~~~~~~~~~ + gst-validate-report.c:122:3: error: ISO C90 forbids mixed declarations and code [-Werror=declaration-after-statement] + unw_cursor_t cursor; + ^~~~~~~~~~~~ + +2016-09-08 12:53:30 -0300 Thibault Saunier + + * validate/config.h.meson: + * validate/configure.ac: + * validate/gst/validate/Makefile.am: + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/meson.build: + * validate/meson.build: + validate: Add backtraces in the reports + Printing them when the reporting all the details only + +2016-09-14 11:31:47 +0200 Sebastian Dröge + + * validate/configure.ac: + configure: Depend on gstreamer 1.9.2.1 + +2016-09-12 14:21:30 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Do not use unset sent_eos variable + And rename class member to sent_eos_time as it is more accurate + +2016-09-09 12:09:45 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + * validate/launcher/utils.py: + * validate/tests/check/meson.build: + * validate/tests/getpluginsdir: + * validate/tests/launcher_tests/meson.build: + * validate/tests/meson.build: + meson:validate:test: Properly set paths to run launcher based tests + Adding a --validate-tools-path option to the launcher, allowing + to pass it from meson. + +2016-09-08 12:52:24 -0300 Thibault Saunier + + * validate/config.h.meson: + * validate/gst/validate/meson.build: + * validate/meson.build: + meson: Build GstValidate as a tracer + And add version to the GStValidate shared library + +2016-09-07 17:24:53 -0300 Thibault Saunier + + * validate/tests/launcher_tests/meson.build: + * validate/tests/launcher_tests/test_validate.py: + * validate/tests/meson.build: + validate: tests: Add launcher based GstValidate tests + First checking the new not negotiated error reporting code. + +2016-09-02 17:39:50 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + validate: launcher: Always clean all tests at the end + Making sure that if an exception of anything happens we will + properly clean all the tests, or at least try to. + +2016-09-02 17:37:24 -0300 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + validate:launcher: Allow specifying expected tests errors + In the future instead of blacklisting tests we should define + what error is expected, and this way when the bug is closed, + we will notice, also, it will allow us to check GstValidate + error reporting itself. + +2016-09-01 17:39:38 -0300 Thibault Saunier + + * validate/configure.ac: + * validate/gst/validate/Makefile.am: + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/meson.build: + * validate/gst/validate/validate.c: + * validate/launcher/baseclasses.py: + * validate/meson.build: + validate: Pass information about GstValidate execution over a socket + Instead of trying to parsing stdout, generate json messages and + send them over a socket so that gst-validate-launcher can properly + have informations about gst-validate subprocess execution. + +2016-08-28 20:37:05 -0300 Thibault Saunier + + * validate/gst/validate/Makefile.am: + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.h: + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + validate: Enhance not-negotiated errors reporting + Keeping negotation information around and trying to figure + out precisely why the elements could not negotied the caps + when we get a NOT_NEGOTIATED error on the bus giving the + user details about it. + +2016-09-07 10:59:22 -0300 Thibault Saunier + + * meson.build: + * validate/meson.build: + * validate/tests/check/getpluginsdir: + * validate/tests/check/meson.build: + * validate/tests/meson.build: + validate:meson: Add tests + +2016-09-06 16:21:05 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + * validate/launcher/reporters.py: + validate:launcher: Use a xunit reporter only when explicitely specified + +2016-09-06 10:29:27 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validare: Enhance report message about wrong position + +2016-09-05 12:16:59 -0300 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + validate:launcher: Report error and exit when a testsuite could not load + Otherwise the user might end up seeing a lot of meaningless logs about + 'removed' tests. + +2016-09-02 17:41:32 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/launcher/apps/gstvalidate.py: + validate: launcher: Fix the condition to check if we need an http server + We could be checking if a string was in None + And use gs_string_assign when assigning the first string + without using printf like format. + +2016-09-02 16:24:47 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + validate: Indent report details + +2016-08-14 16:03:44 -0700 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Wait for ASYNC_DONE to set async state change DONE + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=769894 + +2016-09-05 12:23:35 -0300 Thibault Saunier + + * meson.build: + * meson_options.txt: + meson: Bump version to 1.9.2 + And unify the name of the disable_introspection option + +2016-09-01 12:35:08 +0300 Sebastian Dröge + + * validate/configure.ac: + Back to development + +=== release 1.9.2 === + +2016-09-01 12:35:00 +0300 Sebastian Dröge + + * validate/ChangeLog: + * validate/NEWS: + * validate/configure.ac: + * validate/gst-validate.doap: + Release 1.9.2 + +2016-09-01 03:39:18 +0200 Mathieu Duponchelle + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + launcher: Add --dump-on-failure switch + When the test fails, it can be useful to have the log files dumped + to stdout. + https://bugzilla.gnome.org/show_bug.cgi?id=741092 + +2016-08-28 22:12:35 -0300 Thibault Saunier + + * validate/tools/gst-validate-launcher.in: + validate: Fix launching gst-validate-launcher in a meson based uninstalled env + +2016-08-26 20:06:22 -0300 Thibault Saunier + + * meson.build: + * validate/gst/validate/meson.build: + meson: Add support for building GIR when used as subproject + Add allow project to us it as subproject too + +2016-08-05 15:48:41 -0400 Thibault Saunier + + * .gitignore: + * meson.build: + * meson_options.txt: + * validate/.gitignore: + * validate/config.h.meson: + * validate/data/meson.build: + * validate/data/scenarios/meson.build: + * validate/docs/meson.build: + * validate/docs/validate/meson.build: + * validate/gst/meson.build: + * validate/gst/validate/meson.build: + * validate/launcher/apps/meson.build: + * validate/launcher/meson.build: + * validate/meson.build: + * validate/pkgconfig/meson.build: + * validate/plugins/fault_injection/meson.build: + * validate/plugins/gapplication/meson.build: + * validate/plugins/gtk/meson.build: + * validate/plugins/meson.build: + * validate/tools/gst-validate-launcher.in: + * validate/tools/meson.build: + validate: Add support for Meson as alternative/parallel build system + https://github.com/mesonbuild/meson + +2016-08-13 16:56:18 +0200 Edward Hervey + + * validate/launcher/apps/gstvalidate.py: + validate: Un-blacklist tests that are fixed + the bug reports to which they report have been closed and I can't make + them fail locally. + +2016-08-13 15:39:18 +0200 Edward Hervey + + * validate/launcher/apps/gstvalidate.py: + validate: Blacklist more ogg files + https://bugzilla.gnome.org/show_bug.cgi?id=769545 + +2016-08-12 12:30:41 +0200 Edward Hervey + + * validate/launcher/apps/gstvalidate.py: + validate: Blacklist scrub_forward_seeking.op2b-mpeg2-wave_hd_mxf + See https://bugzilla.gnome.org/show_bug.cgi?id=764025 + +2016-07-28 09:47:42 +0200 Guillaume Desmottes + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/gst/validate/gst-validate-pipeline-monitor.h: + * validate/gst/validate/gst-validate-scenario.c: + * validate/tools/gst-validate.c: + validate: use new API when switching track with playbin3 + Move all the implementations of 'switch-track' to + gst-validate-scenario.c while doing so. + Differential Revision: https://phabricator.freedesktop.org/D1227 + +2016-05-31 12:32:16 +0200 Guillaume Desmottes + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + validate: reporter: break cyclic references with reports + My patch fixing monitor leak (15e7f1bbfd84ce2cc5e6420fee2255c2be95e0f6) + introduced a ref cycle between GstValidateReporter and + GstValidateReport. + The reports uses its reporter so it needs a ref on it + to ensure it's stay alive. But reports are owned by + GstValidateReporter and/or GstValidateRunner. + Fix this by not taking a reference on the reporter but instead caching + its name. + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D1029 + +2016-05-26 14:02:45 +0200 Guillaume Desmottes + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/tests/check/validate/padmonitor.c: + validate: turn GstValidateReport to a mini object + It handles refcounting for us and will enable automatic leak checks when + using the 'leaks' tracer. + Differential Revision: https://phabricator.freedesktop.org/D1233 + +2016-05-26 12:32:16 +0200 Guillaume Desmottes + + * validate/gst-libs/gst/video/gssim.c: + * validate/gst-libs/gst/video/gssim.h: + * validate/gst-libs/gst/video/gstvalidatessim.c: + * validate/gst-libs/gst/video/gstvalidatessim.h: + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-monitor.h: + * validate/gst/validate/gst-validate-override.c: + * validate/gst/validate/gst-validate-override.h: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/gst/validate/media-descriptor.c: + * validate/gst/validate/media-descriptor.h: + validate: inherit from GstObject instead of GObject + This allow us to use to 'leaks' detector to check if those objects are + leaked. + Differential Revision: https://phabricator.freedesktop.org/D1232 + +2016-05-20 15:46:19 +0300 Guillaume Desmottes + + * validate/tools/gst-validate-media-check.c: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: call gst_deinit() after gst_validate_deinit() + This allows validate to clean up before the 'leak' tracer list leaked + objects. + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D1231 + +2016-05-20 15:44:20 +0300 Guillaume Desmottes + + * validate/gst/validate/gst-validate-scenario.c: + validate: use MAY_BE_LEAKED flag + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D1230 + +2016-05-30 15:42:24 +0200 Guillaume Desmottes + + * validate/tools/gst-validate.c: + validate: fix pad leaks + Pads returned using the playbin get-{audio,video}-pad are reffed. + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D1027 + +2016-05-27 15:37:00 +0200 Guillaume Desmottes + + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-element-monitor.c: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-reporter.h: + * validate/gst/validate/media-descriptor-writer.c: + * validate/tools/gst-validate-media-check.c: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: reporter: break cyclic references with reports + My patch fixing monitor leak (15e7f1bbfd84ce2cc5e6420fee2255c2be95e0f6) + introduced a ref cycle between GstValidateReporter and + GstValidateReport. + The reports uses its reporter so it needs a ref on it + to ensure it's stay alive. But reports are owned by GstValidateReporter and/or + GstValidateRunner. + The best way I found to break this cycle is to introduce this purge + method. It's not great but the design is a bit tricky. + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D1029 + +2016-05-27 14:36:44 +0200 Guillaume Desmottes + + * validate/gst/validate/gst-validate-reporter.c: + validate: reporter: prevent usage of destroyed runner + Fix crashes. + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D1028 + +2016-05-27 13:23:48 +0200 Guillaume Desmottes + + * validate/gst/validate/gst-validate-runner.c: + validate: runner: prevent hash table modifications while iterating + A GHashTableIter is invalided if the hash table is modified while we are + iterating. Prevent this by taking the runner lock. + Fix assertion warnings with + validate.file.transcode.to_vorbis_and_vp8_in_webm.Sintel_2010_720p_mkv_srt + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D1026 + +2016-07-29 15:52:48 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate: Add jpeg as known format + +2016-07-29 13:27:23 -0400 Thibault Saunier + + * validate/tests/check/validate/padmonitor.c: + validate: Fix testsuite after additional check for buffer DISCONT flag + +2015-05-19 13:53:06 +0000 Mathieu Duponchelle + + * validate/data/Makefile.am: + data: Fix make distcheck. + by distributing newly-added files. + Reviewed-by: Guillaume Desmottes + Differential Revision: https://phabricator.freedesktop.org/D185 + +2015-10-15 07:29:27 +0000 Wonchul Lee + + * validate/data/scenarios/Makefile.am: + validate: scenario: deploy setup_sink_props_max_lateness config scenario for valgrind + Add to deploy setup_sink_props_max_lateness scenario. + When running gst-validate with valgrind option on the installed package, it fails to find that scenario. + Reviewed-by: Guillaume Desmottes + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D379 + +2016-01-18 03:53:20 +0000 Wonchul Lee + + * validate/gst/validate/gst-validate-scenario.h: + docs: Fix typo + Reviewed-by: Alex Băluț + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D681 + +2016-07-15 08:56:02 -0400 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Add h265, opus and vp9 as known formats + +2016-07-06 13:51:27 +0300 Sebastian Dröge + + * validate/configure.ac: + Back to development + +=== release 1.9.1 === + +2016-07-06 13:48:18 +0300 Sebastian Dröge + + * validate/ChangeLog: + * validate/NEWS: + * validate/configure.ac: + * validate/gst-validate.doap: + Release 1.9.1 + +2016-07-04 16:16:25 +0200 Edward Hervey + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/main.py: + validate: Fix usage for non-standard http server port + Translate the various stream_info URI from the standard port to the + specified port + Make the port option an integer option + +2016-07-04 16:12:27 +0200 Edward Hervey + + * validate/gst/validate/media-descriptor.c: + media-descriptor: Fine-tune stream-id checking + Only expect fully identical stream-id from URI which are not local files + nor from our local http server. + Fixes issues with non-default http server port + +2015-11-23 15:11:58 +0100 Edward Hervey + + * validate/tools/gst-validate.c: + validate: Use presence of STREAM_START to detect track switching + When doing a track switch, the only reliable way to detect that it + happened is whether a new STREAM_START arrives. + Relying on a DISCONT buffer is not satisfactory, since there might + not have been an element setting that flag upstream. + Checking whether the first buffer after a STREAM_START has the + DISCONT flag properly set should be done in parallel + +2015-11-05 10:29:33 +0100 Edward Hervey + + * validate/tools/gst-validate.c: + tools: Make the stream-switching action more generic + This allows it to handle both playbin and playbin3 + +2015-11-05 10:19:22 +0100 Edward Hervey + + * validate/tools/gst-validate.c: + tools: Fix playbin detection + We might be using playbin3 instead + +2015-11-02 14:42:53 +0100 Edward Hervey + + * validate/launcher/apps/gstvalidate.py: + validate: Use decodebin3/uridecodebin3 when specified + Set the USE_PLAYBIN3 environment variable to use those elements instead + of legacy ones. + +2015-11-05 16:37:17 +0100 Edward Hervey + + * validate/tools/gst-validate-analyze: + tools: New tool to view and compare xunit results + +2016-06-24 14:03:48 +0100 Tim-Philipp Müller + + * validate/gst/validate/gst-validate-scenario.h: + validate: fix one more export + gst-validate-scenario.c:183:7: error: '_gst_validate_action_type' redeclared without dllimport attribute: previous dllimport ignore + This is also declared in gst-validate-internal.h + +2016-06-24 11:48:50 +0100 Tim-Philipp Müller + + * validate/gst/validate/gst-validate-internal.h: + validate: fix bogus exports for internal symbols + Hopefully fixes win32 build. + gst-validate-scenario.c:183:7: error: '_gst_validate_action_type' redeclared without dllimport attribute: previous dllimport ignored + gst-validate-scenario.c:286:1: error: '_action_check_and_set_printed' redeclared without dllimport attribute: previous dllimport ignored + gst-validate-scenario.c:298:1: error: 'gst_validate_action_is_subaction' redeclared without dllimport attribute: previous dllimport ignored + gst-validate-scenario.c:305:7: error: '_gst_validate_action_type_type' redeclared without dllimport attribute: previous dllimport ignored + +2016-06-13 17:00:39 -0400 Thibault Saunier + + * validate/gst/validate/validate.c: + validate: Fix Validate plugin paths mixup + +2016-06-10 12:55:47 -0700 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-scenario.c: + validate: fix typo in debug message + +2016-06-09 14:30:53 -0700 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + * validate/launcher/main.py: + validate: fix some recurring typos + +2016-06-09 14:22:31 -0700 Reynaldo H. Verdejo Pinochet + + * validate/tools/gst-validate-transcoding.c: + validate: fix validate-transcoding option descriptions + +2016-06-09 13:57:33 -0700 Reynaldo H. Verdejo Pinochet + + * validate/tools/gst-validate.c: + validate: improve run-time option descriptions + +2016-06-09 16:41:57 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: No execute ON_ADDITION if a previous action has a playback-time + As this is what user will expect in this case. + For example with this scenario: + set-state, state=null; playback-time=5 + set-property, target-element-name=dvbsrc0, property-name=delsys, property-value=11 + play; + +2015-08-11 16:41:20 +0900 Vineeth TM + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: Add support for relative path while providing file path + Instead of providing full absolute path while validating the file, should be + able to provide the relative path with respect to the present directory. + https://bugzilla.gnome.org/show_bug.cgi?id=753494 + +2016-05-24 14:05:37 +0000 Guillaume Desmottes + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/media-descriptor-writer.c: + validate: fix monitor leak when doing frame analysis + The monitor returned by gst_validate_monitor_factory_create() was never + unreffed. + Report instances now have to keep a ref, as suggested by the TODO, as + the reporter is no longer leaked. + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D1012 + +2016-05-24 14:05:30 +0000 Guillaume Desmottes + + * validate/gst/validate/gst-validate-runner.c: + validate: redefine default cat for the runner + The gstvalidate_debug may not be initialized like with the + validate/reporting which was crashing when run with GST_DEBUG=5. + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D1004 + +2016-05-24 14:05:24 +0000 Guillaume Desmottes + + * validate/tools/gst-validate-transcoding.c: + validate: transcoding: fix encoding_profile leak + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D980 + +2016-05-24 14:05:17 +0000 Guillaume Desmottes + + * validate/tools/gst-validate-transcoding.c: + validate: transcoding: fix caps leaks + The 'all_raw_caps' list is never used and was just leaking caps. + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D979 + +2016-05-24 14:05:10 +0000 Guillaume Desmottes + + * validate/gst/validate/gst-validate-scenario.c: + validate: scenario: always unref srcpad + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D959 + +2016-05-24 14:05:03 +0000 Guillaume Desmottes + + * validate/gst/validate/media-descriptor-writer.c: + validate: media-descriptor-writer: fix pad leaks + - the pad returned by gst_element_get_static_pad() was leaked. + - unref the pad from snode when updating it, not the pad passed as + callback to pad_added_cb() + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D958 + +2016-05-24 14:04:57 +0000 Guillaume Desmottes + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/gst/validate/validate.c: + validate: clean up action_types list on deinit + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D957 + +2016-05-24 14:04:50 +0000 Guillaume Desmottes + + * validate/gst/overrides/gst-validate-default-overrides.c: + * validate/gst/validate/gst-validate-override-registry.c: + * validate/plugins/ssim/gstvalidatessim.c: + validate: keep a ref when registering an override + _add_override_from_struct() could, in theory, register more than once + the same override so we should not transfer the ref. + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D956 + +2016-05-24 14:04:44 +0000 Guillaume Desmottes + + * validate/gst/validate/gst-validate-override-registry.c: + validate: use g_object_unref() on GstValidateOverride + Those are GObject subclasses, not GstObject. + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D955 + +2016-05-24 14:04:37 +0000 Guillaume Desmottes + + * validate/data/gstvalidate.supp: + validate: add mesa-related valgrind suppressions + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D954 + +2016-05-24 14:04:31 +0000 Guillaume Desmottes + + * validate/launcher/baseclasses.py: + validate: generate valgrind suppression traces + Makes fixing easier as then we can just re-use the generated trace. + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D953 + +2016-05-24 14:04:25 +0000 Guillaume Desmottes + + * validate/launcher/vfb_server.py: + validate: fix typo if Xvfb is not installed + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D375 + +2016-05-23 15:57:04 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: Don't check for DISCONT flags on buffer in pull-mode + It only makes sense in push-mode + +2016-05-20 09:07:01 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: Update checks for MISSING_DISCONT + * Some SEGMENT might be updates caused by calling gst_pad_set_offset(), + which will send the same segment but with an updated offset and/or + based field. For those segments, we don't require a DISCONT on the + following buffer. + * Ignore differences in flags, they aren't relevant for now to figure + out whether the segment is an update or not + * Ignore difference in 'position', it's only meant for internal usage + by elements. + * Changes in the end position (stop in forward playback and start in + reverse playback) are considering updates + Furthermore, also expect a DISCONT flag on the first buffer following + a STREAM_START. + +2016-05-17 10:03:26 +0200 Edward Hervey + + * validate/tools/gst-validate.c: + gst-validate: Avoid overreading array + If we offset the argv table as argument, we need to decrement the number + of items in the array (argc) when iterating it + +2016-05-19 11:59:19 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.h: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + pad_monitor: Add a check for buffer DISCONT flag + The first buffer after a FLUSH or SEGMENT should have the DISCONT flag + set. + +2016-05-15 06:34:21 -0300 Thiago Santos + + * validate/launcher/main.py: + launcher: set gsettings-backend to prevent weird deadlocks + It seems like some sort of forking/dconf/gtype combination can + deadlock occasionally. Setting the gsettings backend to memory + makes it go away. + Same issue: https://cgit.freedesktop.org/gstreamer/gst-plugins-good/commit/tests/check/Makefile.am?id=8e2c1d1de56bddbff22170f8b17473882e0e63f9 + +2016-05-06 21:27:53 -0300 Thiago Santos + + * validate/launcher/apps/gstvalidate.py: + validate: add non-seekable fragment file seek tests to the blacklist + The file has no index and is not easily seekable, seeking in these + kind of files isn't implemented. + +2016-05-03 15:47:32 -0400 Nicolas Dufresne + + * debug-viewer/gst-debug-viewer: + debug-viewer: Allow running uninstalled with symlink + When uninstalled, we look at the directory of the executable to find + the resources. This patch uses realpath in replacement to abspath so + the path get expended, and symlink are followed. + +2016-04-13 21:18:28 +0200 Alexandru Băluț + + * validate/plugins/gtk/gstvalidategtk.c: + validate: Stop using deprecated method + https://bugzilla.gnome.org/show_bug.cgi?id=764964 + +2016-04-07 14:11:45 +0200 Edward Hervey + + * validate/data/scenarios/scrub_backward_seeking.scenario: + * validate/data/scenarios/scrub_backward_seeking_full.scenario: + scenarios: Fix scrub_backward scenarios + We will be changing states, set the handles-state variable accordingly + +2016-03-18 10:42:55 +0100 Xabier Rodriguez Calvar + + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/data/menus.ui: + Added menu opts to filter in instead of only out + Added also menu option to filter a log level and all above that + https://bugzilla.gnome.org/show_bug.cgi?id=763857 + +2016-03-18 10:42:18 +0100 Xabier Rodriguez Calvar + + * debug-viewer/GstDebugViewer/GUI/filters.py: + Added support to filter in instead of only out + Added support to filter a log level and all above it + https://bugzilla.gnome.org/show_bug.cgi?id=763857 + +2016-04-04 22:23:26 +0200 Stefan Sauer + + * debug-viewer/GstDebugViewer/GUI/models.py: + filter: add more logging + +2016-03-25 22:20:11 +0100 Stefan Sauer + + * debug-viewer/GstDebugViewer/Common/GUI.py: + menu: port menu.popup calls + Add 1 extra arg. + +2016-01-14 12:21:34 +0100 Philippe Normand + + * debug-viewer/GstDebugViewer/GUI/columns.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + debug-viewer: use the gi GLib version rather than the old gobject glib package + +2016-01-14 12:19:22 +0100 Philippe Normand + + * debug-viewer/GstDebugViewer/Common/GUI.py: + debug-viewer: explicitely require GTK+3 + +2016-01-12 09:44:17 +0100 Stefan Sauer + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/README: + debug-viewer: add more logging and some profiling how-to + The port is much slower than the gtk3 version. Try to figure why. + +2015-10-21 15:03:03 +0200 Stefan Sauer + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + * debug-viewer/README: + GstDebugViewer/Timeline: port timeline widgets to gtk3 + +2015-10-20 15:21:01 +0200 Stefan Sauer + + * debug-viewer/GstDebugViewer/Common/Data.py: + * debug-viewer/GstDebugViewer/Common/GUI.py: + * debug-viewer/GstDebugViewer/Common/Main.py: + * debug-viewer/GstDebugViewer/Common/__init__.py: + * debug-viewer/GstDebugViewer/Common/generictreemodel.py: + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI/__init__.py: + * debug-viewer/GstDebugViewer/GUI/app.py: + * debug-viewer/GstDebugViewer/GUI/colors.py: + * debug-viewer/GstDebugViewer/GUI/columns.py: + * debug-viewer/GstDebugViewer/GUI/models.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/GstDebugViewer/Plugins/FileProperties.py: + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + * debug-viewer/README: + * debug-viewer/tests/performance.py: + GstDebugViewer: basic port to gtk3 and python gobject + +2016-03-25 14:17:28 +0000 Tom Schoonjans + + * validate/gst-libs/gst/video/Makefile.am: + gst-validate: Link with GIO and clean up CFLAGS/LIBADD/LDFLAGS + https://bugzilla.gnome.org/show_bug.cgi?id=764192 + +2016-03-07 08:55:27 +0900 Vineeth T M + + * validate/tests/check/validate/test-utils.c: + validate: use new gst_element_class_add_static_pad_template() + https://bugzilla.gnome.org/show_bug.cgi?id=763197 + +2016-03-07 08:53:23 +0900 Vineeth T M + + * codecanalyzer/src/plugins/gst/analyzersink/gstanalyzersink.c: + codecanalyzer: use new gst_element_class_add_static_pad_template() + https://bugzilla.gnome.org/show_bug.cgi?id=763197 + +2016-03-24 13:34:03 +0200 Sebastian Dröge + + * validate/configure.ac: + Back to development + +=== release 1.8.0 === + +2016-03-24 13:11:29 +0200 Sebastian Dröge + + * validate/ChangeLog: + * validate/NEWS: + * validate/configure.ac: + * validate/gst-validate.doap: + Release 1.8.0 + +2016-03-23 20:02:47 +0100 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + validate: launcher: Make sure to properly setup all testsuites + When a first testsuite will set paths, it does not mean that we should + just register following testsuite test manager default tests. + So we need to make a difference between the media paths the user passed + with --media-path and the ones defined by the testsuite. + +2016-03-23 19:34:10 +0100 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Handle testslist files even running several testsuites + Only if those testsuites do not use the same Tester as we + currently can't know to what testsuite a test belongs. + +2016-03-23 11:48:10 +0100 Thibault Saunier + + * validate/launcher/reporters.py: + launcher: Avoid caching all the debug logs in memory + And just write the temporary XML file on disc + +2016-03-22 19:00:48 +0100 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate: launcher: Blacklist dash and HLS failling seeking tests + As described in https://bugzilla.gnome.org/show_bug.cgi?id=764020 + +2016-03-22 17:46:50 +0100 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate:testsuite: Blacklist validate.hls.playback.scrub_forward_seeking.hls_bibbop as it is racy + +2016-03-22 12:07:08 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + validate: pad-monitor: Check right segment after seek + After a seek we need to wait for the right segment (meaning the segment + with seqnum == last seek/flush stop seqnum) to check whether the segment.time + has been properly set. + +2016-03-22 11:19:42 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pipeline-monitor.c: + validate: Accept pad return FLUSHING when the element is being teared down + In the case and element is in READY or is going to READY state, it can + always return GST_FLOW_FLUSHING. + Avoid a race where a demuxer sinkpad has not been set to FLUSHING when we are + still processing a buffer but downstream is already FLUSHING and thus + the demuxer is already returning FLUSHING. + +2016-03-21 14:34:27 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/launcher/baseclasses.py: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: Use GstValidate logging system to print buffering avancement + So it can be used in the launcher. + And make sure to properly parse the info in the launcher. + +2016-03-09 10:05:49 +0100 Thibault Saunier + + * validate/launcher/httpserver.py: + validate:launcher: Remove dependency on wget + +=== release 1.7.91 === + +2016-03-15 12:40:03 +0200 Sebastian Dröge + + * validate/ChangeLog: + * validate/NEWS: + * validate/configure.ac: + * validate/gst-validate.doap: + Release 1.7.91 + +2016-03-14 14:29:57 +0200 Sebastian Dröge + + * validate/gst/validate/gst-validate-scenario.c: + validate: Add missing parenthesis to seek position check + https://bugzilla.gnome.org/show_bug.cgi?id=763602 + +2016-03-14 12:55:57 +0200 Sebastian Dröge + + * validate/gst/validate/gst-validate-scenario.c: + validate: Fix overflow seek position comparision + MAX(0, ((gint64) priv->segment_start - priv->seek_pos_tol) will be a high + positive number thanks to being interpreted as unsigned values if + segment_start < seek_pos_tol. Fix this by explicitly checking for this case + and only doing the subtraction otherwise. + This fixes the problem from fdccffbb2e5885b3f8e7369cdbda45b6717ffab0 + completely now. + https://bugzilla.gnome.org/show_bug.cgi?id=763602 + +2016-03-08 17:01:09 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/gst-validate-utils.h: + * validate/gst/validate/media-descriptor-parser.c: + * validate/gst/validate/media-descriptor-writer.c: + * validate/gst/validate/media-descriptor.c: + * validate/gst/validate/media-descriptor.h: + validate: Fix seding mistakes + When we added namespace to make GI happy we ended up with + structure called like GstValidateMediaGstValidateMediaXXX. + +2016-03-08 10:49:43 +0100 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.h: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + monitor: Add critical issue for checking accurate seek results + If an accurate seek is accepted, the resulting segment.time should be + exactly the requested seek start value.. + https://bugzilla.gnome.org/show_bug.cgi?id=763299 + +2016-03-08 12:00:19 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Fix accurate seeking in paused failling condition + +2016-03-08 15:54:32 +0900 Vineeth T M + + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-scenario.h: + * validate/gst/validate/gst-validate-utils.c: + * validate/launcher/httpserver.py: + * validate/launcher/utils.py: + * validate/plugins/gtk/gstvalidategtk.c: + * validate/tools/gst-validate-images-check.c: + Validate: Fix consider, launch spelling mistakes + https://bugzilla.gnome.org/show_bug.cgi?id=763289 + +2016-02-22 11:49:48 +0900 Vineeth T M + + * validate/launcher/apps/gstvalidate.py: + validate: Fix wrong condition check when adding tests + When listing tests, checking whether uri is present or not and displaying error. + But uri does notneed to be present in case of pipeline generator. So the condition check is wrong. + This results in validateelements testsuite not working. Hence modifying the condition to + not error out on valid cases. + https://bugzilla.gnome.org/show_bug.cgi?id=762422 + +=== release 1.7.90 === + +2016-03-01 19:23:37 +0200 Sebastian Dröge + + * validate/ChangeLog: + * validate/NEWS: + * validate/configure.ac: + * validate/gst-validate.doap: + Release 1.7.90 + +2016-03-01 14:59:29 +0100 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Strip os.pathsep from extra env variables + We might be working with something that is not a path + +2016-02-29 15:55:47 +0100 Thibault Saunier + + * validate/gst/validate/media-descriptor-writer.c: + validate: Add a missing new line to media info files + +2016-02-23 12:15:21 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-runner.c: + validate: Fix build + +2016-02-23 11:29:56 +0100 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + validate:launcher: Add a way to fail if test have been removed/added + +2016-01-22 20:55:27 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-media-info.c: + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/gst-validate-override.c: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/media-descriptor.c: + validate: Fix annotations + Skipping all functions and methods that are not relevant and not easily + introspectable + Differential Revision: https://phabricator.freedesktop.org/D706 + +2016-01-22 20:45:01 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-scenario.h: + validate: Remove unimplemented function + Differential Revision: https://phabricator.freedesktop.org/D705 + +2016-01-22 20:14:16 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-element-monitor.c: + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-monitor.h: + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.h: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/gst-validate-utils.h: + * validate/gst/validate/media-descriptor-parser.c: + * validate/gst/validate/media-descriptor-parser.h: + * validate/gst/validate/media-descriptor-writer.c: + * validate/gst/validate/media-descriptor-writer.h: + * validate/gst/validate/media-descriptor.c: + * validate/gst/validate/media-descriptor.h: + * validate/tests/check/validate/padmonitor.c: + * validate/tools/gst-validate-media-check.c: + * validate/tools/gst-validate.c: + validate: Namespace all our structures and objects + Making GI a bit happier. + Those are not stable API anyway... + Differential Revision: https://phabricator.freedesktop.org/D704 + +2016-01-22 19:50:15 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-element-monitor.c: + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/gst/validate/media-descriptor-parser.c: + * validate/gst/validate/media-descriptor-parser.h: + * validate/gst/validate/media-descriptor-writer.c: + * validate/gst/validate/media-descriptor-writer.h: + * validate/gst/validate/media-descriptor.h: + * validate/gst/validate/validate.c: + validate: Misc annotation and gi friendly cleanups + Differential Revision: https://phabricator.freedesktop.org/D703 + +2016-01-22 19:38:53 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-element-monitor.c: + * validate/gst/validate/gst-validate-monitor-factory.c: + validate: Guarantee that we never create 2 monitors for the same object + Differential Revision: https://phabricator.freedesktop.org/D702 + +2015-10-24 09:28:51 +0200 Thibault Saunier + + * validate/configure.ac: + * validate/gst/Makefile.am: + * validate/gst/preload/Makefile.am: + * validate/gst/preload/gst-validate-monitor-preload.c: + * validate/gst/validate/Makefile.am: + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-runner.h: + * validate/gst/validate/validate.c: + * validate/plugins/fault_injection/Makefile.am: + * validate/plugins/gapplication/Makefile.am: + * validate/plugins/gtk/Makefile.am: + * validate/plugins/ssim/Makefile.am: + * validate/tests/check/validate/monitoring.c: + * validate/tests/check/validate/overrides.c: + * validate/tests/check/validate/padmonitor.c: + * validate/tests/check/validate/reporting.c: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: Turn GstValidateRunner into a GstTracer + This way we do not need the LD_PRELOAD hack anymore + Add a new libgstvalidateplugin GStreamer plugin, making sure it shares + the exact same code as the library (exposing only the wanted symbols). + Fix the way we set where to install GstValidate plugins + Try to keep backward compatibility even if tracers should never be instantiated + after an GstElement has been instantiated. + Differential Revision: https://phabricator.freedesktop.org/D459 + +2016-01-22 12:58:31 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-reporter.c: + reporter: Properly reset g_log handler when reporter is destroyed + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D699 + +2016-01-19 11:10:49 +0100 Thibault Saunier + + * validate/gst/validate/validate.c: + validate: Make _deinit thread safe + Reviewed-by: Thibault Saunier + Differential Revision: https://phabricator.freedesktop.org/D698 + +2015-10-08 10:19:39 +0900 Vineeth T M + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: handle spaces in transcode output path + When there are spaces in transcoding output path, then it fails. Hence adding the + path in double quotes + https://bugzilla.gnome.org/show_bug.cgi?id=756217 + +2015-11-12 09:04:01 +0900 Vineeth T M + + * validate/launcher/baseclasses.py: + validate:launcher: Add proper check for is_seekable + The seekable variable in media_info file is of type string. When checking if the file + is seekable using is_seekable, it just returns the string, resulting in it always being true. + It should actually be comparing the string and returning true or false based on comparison + https://bugzilla.gnome.org/show_bug.cgi?id=755854 + +2015-08-27 11:16:39 +0900 Vineeth T M + + * validate/data/scenarios/fast_forward.scenario: + validate: fast_forward: Calculate proper playback-time for scenario + In case of fast-forward scenario, the playback-time is not set properly + as per increase in the rate. This is resulting in short media files of duration + less that 15 seconds to fail. + https://bugzilla.gnome.org/show_bug.cgi?id=754151 + +2016-01-28 14:59:08 -0800 Reynaldo H. Verdejo Pinochet + + * codecanalyzer/src/gst_analyzer.h: + codecanalyzer: fix typo leading to implicit decl warning at build-time + +2016-01-28 14:48:17 -0800 Reynaldo H. Verdejo Pinochet + + * codecanalyzer/src/codecanalyzer.c: + * codecanalyzer/src/gst_analyzer.c: + * codecanalyzer/src/plugins/gst/analyzersink/mpeg_xml.c: + * codecanalyzer/src/xml_parse.c: + codecanalyzer: do not use g_error if abort is not desired + Use g_printerr() instead. + g_error() calls abort after outputting the message + so these blocks' return statements and free()s + were unreachable. + Aditionally, fix wrong void returns on non-void + function, drop trailing whitespace before newline and + add \n's as needed (default handler won't add one). + +2016-01-28 14:34:37 -0800 Reynaldo H. Verdejo Pinochet + + * codecanalyzer/src/codecanalyzer.c: + * codecanalyzer/src/gst_analyzer.c: + codecanalyzer: add missing includes for g_printf() + +2016-01-28 14:25:59 -0800 Reynaldo H. Verdejo Pinochet + + * codecanalyzer/src/codecanalyzer.c: + codecanalyzer: Update README URL in help dialog + Previous one was a 404 + +2016-01-28 11:22:15 -0800 Reynaldo H. Verdejo Pinochet + + * configure: + configure: fix typos + +2016-01-21 15:05:52 +0100 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: More fixes of previous commit + You'd think in 2016 compilers could complain when assigning/comparing + different types of enums ... *sigh*. + +2016-01-20 13:42:31 +0100 Edward Hervey + + * validate/tools/gst-validate.c: + tools: Fix relative track switching + I have no idea where that "-2" came from, but it was obviously wrong. + Just use modulo "total number of streams" to get the proper track id. + +2016-01-21 14:23:24 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + validate: Remove debugging left overs + +2016-01-21 14:17:40 +0100 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.h: + pad-monitor: Add support for GstPadEventFullFunc + This ensures our sink pad event wrapper is properly called if the + element implement a GstPadEventFullFunc instead of a regular one. + Removes all stray "buffer received before segment" issues with + queue/multiqueue + +2016-01-21 11:13:55 +0100 Thibault Saunier + + * validate/tests/check/validate/padmonitor.c: + * validate/tests/check/validate/reporting.c: + * validate/tests/check/validate/test-utils.c: + * validate/tests/check/validate/test-utils.h: + validate: Fix testsuite + Use fake elements instead of real ones in our tests so that + we control exactly the number of issues generated. + Until now we were trying to hide extra issues with a probe dropping + events and buffers but since 2dfa548f3645844082c3db65d96d87255701b3ad + "pad: Append hooks instead of prepending to call them in the order they were added" + in core, hidding will not work. + +2016-01-19 11:31:37 +0100 Thibault Saunier + + * validate/gst/validate/Makefile.am: + g-i: fix init section to avoid compiler warnings + +2016-01-15 20:13:59 +0000 Thibault Saunier + + * validate/gst/validate/gst-validate-runner.c: + runner: Report criticals when the reporter is in smart mode + +2015-11-26 17:08:12 -0300 Thiago Santos + + * validate/gst/validate/media-descriptor-parser.c: + * validate/gst/validate/media-descriptor-writer.c: + * validate/gst/validate/media-descriptor.c: + * validate/gst/validate/media-descriptor.h: + media-descriptor-writer: track running time of buffers + PTS and DTS can be deceiving as a change in segment can dramatically change + playback synchronization. Track the running-time as well to properly + get any change in synchronization + +2015-12-10 14:10:54 +0100 Thibault Saunier + + * configure: + Add a toplevel configure script to build components at once + And this way respect https://github.com/cgwalters/build-api + +2015-11-30 11:54:05 -0300 Thiago Santos + + * validate/gst/validate/media-descriptor.c: + * validate/gst/validate/media-descriptor.h: + * validate/tools/gst-validate-media-check.c: + media-check: enable 'full' for files which reference are also 'full' + If the reference file has frames information, enable it automatically + so that the comparison file also has frames to be used. + https://bugzilla.gnome.org/show_bug.cgi?id=758855 + +2015-11-27 18:05:23 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/media-descriptor.c: + media-descriptor: check if frame data matches + When comparing media descriptors, also check if the frames + match + https://bugzilla.gnome.org/show_bug.cgi?id=758855 + +2015-11-26 16:30:20 -0300 Thiago Santos + + * validate/gst/validate/media-descriptor-writer.c: + media-descriptor-writer: refactor getting the streamnode by a pad + https://bugzilla.gnome.org/show_bug.cgi?id=758855 + +2015-11-26 17:20:20 -0300 Thiago Santos + + * validate/gst/validate/media-descriptor-writer.c: + media-descriptor-writer: remove condition from inside loop + Only enter the loop if it indeed has a change of doing something + +2015-11-30 11:00:07 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-media-info.c: + * validate/gst/validate/gst-validate-scenario.c: + docs: fix typo + Retrive -> retrieve + +2015-12-02 13:50:02 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + validate: Add some documentation and annotiations + +2015-12-02 13:49:01 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-enums.h: + * validate/gst/validate/gst-validate-runner.c: + validate: Add a 'smart' reporting details mode + Making sure to show all informations for critical issues, but be synthetic for others + +2015-11-23 17:44:27 +0100 Edward Hervey + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Set the default action execution interval to 10ms + Having a default value of 0 meant that a g_idle_add loop was constantly + running, causing each test to use 100% cpu. + This is no longer required. Using a 10ms interval brings down cpu usage + to a sane value + +2015-08-20 16:54:14 +0900 Vineeth T M + + * codecanalyzer/src/codecanalyzer.c: + codecanalyzer: Fix memory leaks when context parse fails + When g_option_context_parse fails, context and error variables are + not getting free'd which results in memory leaks being reported. + https://bugzilla.gnome.org/show_bug.cgi?id=753862 + +2015-08-24 12:30:57 +0900 Vineeth T M + + * validate/gst/validate/media-descriptor-writer.c: + validate: media-descriptor-writer: Don't create media info when stream info is not present. + When a file does not contain any stream info, then there is no need + to create the media info file as, it is not considered to be a valid file + and no validate checks are done for the same. + This skips unnecessary files like .txt, .dump files + https://bugzilla.gnome.org/show_bug.cgi?id=754006 + +2015-11-16 16:45:13 +0100 Thibault Saunier + + * validate/launcher/main.py: + validate: launcher: Set scenario manager config before discovering testsuites + +2015-10-05 13:38:10 +0900 Vineeth T M + + * validate/launcher/apps/gstvalidate.py: + validate: launcher: Print error when media-info files not present + When there are no media-info files present and --generate-media-info + option is not given, then it just fails without printing error. + Printing an error stating, use --generate-media-info if there are no + media info files. + When there are neither media files and media info files, print error + stating the same + https://bugzilla.gnome.org/show_bug.cgi?id=755087 + +2015-08-20 16:51:03 +0900 Vineeth T M + + * validate/tools/gst-validate-images-check.c: + * validate/tools/gst-validate-media-check.c: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: Fix memory leaks when context parse fails + When g_option_context_parse fails, context and error variables are not getting free'd + which results in memory leaks. Free'ing the same. + And replacing g_error_free with g_clear_error, which checks if the error being passed + is not NULL and sets the variable to NULL on free'ing. + https://bugzilla.gnome.org/show_bug.cgi?id=753862 + +2015-08-05 13:40:52 +0900 Vineeth TM + + * validate/data/gstvalidate.supp: + validate: suppression: suppress 'uninitialised value of size 4' in aacdec + Suppress this error, until the logic in libav is fixed. + https://bugzilla.gnome.org/show_bug.cgi?id=753268 + +2015-11-10 17:43:54 +0100 Thibault Saunier + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + validate: Implement support to run tests inside gdb + Making debugging races leading to crashes easier to debug + +2015-11-10 15:14:49 +0100 Thibault Saunier + + * validate/docs/validate/gst-validate-transcoding.xml: + validate: Minor documentation fix + +2015-11-08 01:37:14 +0100 Thibault Saunier + + * validate/docs/validate/gst-validate-transcoding.xml: + validate: Remove 0.10 caps reference from the documentation + +2015-10-29 14:53:53 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + validate: Handle setting several scenarios + The user might have scenarios specific to a particular pipeline, and the + application might have several pipelines running and scenarios that + apply on specific pipeline. We have to handle that valid use case. + +2015-10-27 15:51:44 +0200 Sebastian Dröge + + * validate/launcher/apps/gstvalidate.py: + validate: Un-blacklist seeking HLS tests + They are reliable enough after 36b80edb7263118467dfcaee3923f7c964ae6bc8 + in gst-plugins-base now. + +2015-10-26 15:35:42 +0100 Wonchul Lee + + * validate/gst/validate/Makefile.am: + validate: Add missing gir include path + Add missing gir include path for building with gst-uninstalled script + Differential Revision: https://phabricator.freedesktop.org/D461 + +2015-10-08 09:58:25 +0900 Vineeth T M + + * validate/tools/gst-validate-transcoding.c: + validate-transcoding: trivial patch to change error from 0.10 to 1.0 + While printing error in transcoding, gst-validate-transcoding-0.10 is being used. + Changing the same to 1.0 + https://bugzilla.gnome.org/show_bug.cgi?id=756215 + +2015-10-14 11:56:56 +0100 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate: Add support for prores + +2015-10-10 10:51:10 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-scenario.c: + validate: fix double free + Summary: + Move variable declarations in the for block so we won't try re-free + tldir in case of early short circuiting of the 'for' code. + Depends on D348 + Reviewers: thiblahute + Reviewed By: thiblahute + Differential Revision: https://phabricator.freedesktop.org/D349 + +2015-10-10 10:50:58 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-scenario.c: + validate: fix loading of full path scenario + Summary: + We were checking if the path was a full one but was using the + scenario_name instead of this path when trying to load the scenario. + Depends on D346 + Reviewers: thiblahute + Reviewed By: thiblahute + Differential Revision: https://phabricator.freedesktop.org/D348 + +2015-10-10 10:47:40 +0100 Thibault Saunier + + * .arcconfig: + Update .arcconfig + +2015-07-27 08:46:01 +0900 Vineeth TM + + * validate/launcher/baseclasses.py: + validate:launcher: throw valgrind error only for definite loss + errors-for-leak-kinds should be set to definite, because almost every test case + , will have possibly lost memory, which may or may not be a leak. + And throwing error for all these cases doesn't seem to be correct. + https://bugzilla.gnome.org/show_bug.cgi?id=752754 + +2015-08-07 21:38:20 +0900 Vineeth T M + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + validate: launcher: Fix media_check class name and add double quotes for valgrind logs + When creating the class names for media check, uri is being used, + instead of the path. Hence converting the uri using uri2path and creating + class name. + Add double quotes for valgrind logs, to support special characters like space + https://bugzilla.gnome.org/show_bug.cgi?id=752808 + +2015-08-17 10:40:22 +0900 Vineeth TM + + * validate/tools/gst-validate-media-check.c: + validate: media-check: Pass NULL instead of GError if not using it + If not using the GError being passed on to media descriptor, writer and parser, + simply pass NULL instead of GError. + https://bugzilla.gnome.org/show_bug.cgi?id=753340 + +2015-08-17 10:31:33 +0900 Vineeth TM + + * validate/gst/validate/media-descriptor-writer.c: + validate: descriptor-writer: Handle NULL GError address and free GError during error cases + writer_new_discover() API should be able to accept NULL GError and in case of + error, if GError is passed on as parameter, it should be propagated, else it + should be free'd. + https://bugzilla.gnome.org/show_bug.cgi?id=753340 + +2015-08-11 10:05:41 +0900 Vineeth TM + + * validate/tools/gst-validate-images-check.c: + * validate/tools/gst-validate-media-check.c: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate:tools: set locale to all and change argument to FILENAME + When file name consists of characters from other languages, say korean, + then it throws an error + Error initializing: Invalid byte sequence in conversion input + Hence setting locale to all to fix this. + And changing the media-info argument to type G_OPTION_ARG_FILENAME + https://bugzilla.gnome.org/show_bug.cgi?id=753486 + +2015-08-24 16:39:15 +0900 Vineeth T M + + * validate/tools/gst-validate-images-check.c: + * validate/tools/gst-validate-media-check.c: + validate: Print the return value at the end + Makes it easier to know if the test passed or failed. + https://bugzilla.gnome.org/show_bug.cgi?id=754013 + +2015-08-26 10:36:51 +0900 Vineeth T M + + * validate/launcher/apps/gstvalidate.py: + validate: launcher: Support relative path for folder names + Even though relative paths are supported, right now it does not + work when we give the path as 'media/' present in the current directory. + Adding support for the same. + https://bugzilla.gnome.org/show_bug.cgi?id=754100 + +2015-09-30 18:13:28 +0200 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Treat min-media-duration as a float + +2015-09-30 15:02:03 +0200 Thibault Saunier + + * validate/data/scenarios/switch_audio_track.scenario: + scenarios: Set min-media-duration on switch_audio_track + +2015-09-30 14:55:37 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Add a way to define a timeout for actions execution + Reviewers: Mathieu_Du + Differential Revision: https://phabricator.freedesktop.org/D271 + +2015-09-26 18:46:05 +0200 Sebastian Dröge + + * validate/plugins/ssim/gstvalidatessim.c: + validatessim: Stop using deprecated gst_segment_to_position() + +2015-09-25 12:51:31 +0200 Thibault Saunier + + * validate/configure.ac: + Back to development + +=== release 1.6.0 === + +2015-09-25 12:50:13 +0200 Thibault Saunier + + * validate/ChangeLog: + * validate/NEWS: + * validate/configure.ac: + * validate/gst-validate.doap: + Release 1.6.0 + +2015-09-16 17:12:17 +0900 eunhae choi + + * validate/data/scenarios/switch_subtitle_track.scenario: + * validate/data/scenarios/switch_subtitle_track_while_paused.scenario: + validate: scenarios: set need clock sync for switching subtitle track + As soon as the track is changed, the pipeline state is set to NULL + by execution 'stop' action even if there is a 'playback-time' with 5sec. + If the AV sink is not synchronized, + audio fakesink and video fakesink has different position value. + When the validate request the position information of pipeline + to do 'stop' action, the audio fakesink response of the position query + with the bigger value than 5sec. + https://bugzilla.gnome.org/show_bug.cgi?id=755101 + +=== release 1.5.90 === + +2015-08-20 17:58:36 +0200 Thibault Saunier + + * validate/ChangeLog: + * validate/NEWS: + * validate/configure.ac: + * validate/gst-validate.doap: + Release 1.5.90 + +2015-08-21 11:09:03 +0200 Thibault Saunier + + * validate/common: + Update common submodule + +2015-08-20 16:35:15 +0200 Thibault Saunier + + * validate/configure.ac: + * validate/launcher/config.py.in: + validate: Put the GstValidate testsuite version in the launcher config + This way testsuite implementation can have the information + +2015-08-17 17:20:07 +0200 Sebastian Dröge + + * validate/tools/gst-validate-transcoding.c: + validate/transcoding: Don't override the target state of the scenario when receiving BUFFERING=100% + If the scenario handles the states and wants to stay in PAUSED, it's not a + good idea to change the state to PLAYING when receiving BUFFERING=100%. This + caused a race condition in varios seeking tests, most often in the dash scrub + seeking test. + +2015-08-17 14:27:33 +0200 Sebastian Dröge + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/tools/gst-validate.c: + validate: Don't override the target state of the scenario when receiving BUFFERING=100% + If the scenario handles the states and wants to stay in PAUSED, it's not a + good idea to change the state to PLAYING when receiving BUFFERING=100%. This + caused a race condition in varios seeking tests, most often in the dash scrub + seeking test. + +2015-08-16 17:59:00 +0200 Sebastian Dröge + + * validate/gst/validate/gst-validate-scenario.c: + validate: Fix typo + +2015-08-16 17:53:28 +0200 Sebastian Dröge + + * validate/launcher/apps/gstvalidate.py: + validate/launcher: Blacklist some HLS seeking tests again + +2015-08-16 08:50:36 -0300 Thiago Santos + + * validate/launcher/apps/gstvalidate.py: + validate/launcher: Un-blacklist validate.dash.playback.reverse_playback.* + Fixed now + +2015-08-16 12:26:16 +0200 Sebastian Dröge + + * validate/launcher/apps/gstvalidate.py: + validate/launcher: Un-blacklist validate.hls.playback.reverse_playback.* + It apparently succeeds now. + +2015-08-15 19:07:02 +0200 Sebastian Dröge + + * validate/launcher/apps/gstvalidate.py: + validate/launcher: De-blacklist some HLS tests + +2015-08-15 19:04:14 +0200 Sebastian Dröge + + * validate/launcher/apps/gstvalidate.py: + validate/launcher: Blacklist some DASH tests + +2015-08-15 16:40:11 +0200 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + launcher: Handle override files for media files + And make sure to create a new dict for extra_env_vars when instanciating + GstValidateTest + +2015-08-15 16:23:02 +0200 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + launcher: Make sure MediaDescriptor is set in the GstValidateTest class itself + It is used there but was set in each and every subclasses + +2015-08-15 16:19:24 +0200 Sebastian Dröge + + * validate/launcher/utils.py: + validate/launcher: Treat DASH like HLS in another place + +2015-08-07 12:51:53 +0900 Vineeth TM + + * validate/gst/validate/gst-validate-scenario.c: + validate: scenario: fix leak during error cases + When message_async is not called during error cases, needs_parsing GList is + not being freed resulting in leak. Hence free'ing the same in finalize. + https://bugzilla.gnome.org/show_bug.cgi?id=753339 + +2015-07-23 15:51:09 +0900 Vineeth TM + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/media-descriptor-writer.c: + validate: descriptor-writer: Handle error when stream info is not available + There is no check to see if stream info is available. This leads to + assertion error. Adding proper error messages for the same and reported + the same as a validate warning message. + https://bugzilla.gnome.org/show_bug.cgi?id=752758 + +2015-07-23 15:08:55 +0900 Vineeth TM + + * validate/gst/validate/media-descriptor-writer.c: + validate: descriptor-writer: Print proper error message when discover fails + When discovering the files, there will be different kind of errors. If we print + the exact message, then it will be more helpful for user. Especially in the case + of missing plugins, displaying which plugin is missing as error message + https://bugzilla.gnome.org/show_bug.cgi?id=752758 + +2015-07-23 13:35:04 +0900 Vineeth TM + + * validate/gst/validate/gst-validate-override-registry.c: + validate:override-registry: fix memory leak + mutex is being initialized but not cleared. + https://bugzilla.gnome.org/show_bug.cgi?id=752754 + +2015-07-23 11:08:18 +0900 Vineeth TM + + * validate/tools/gst-validate-transcoding.c: + validate: tools: transcoding error due to wrong condition check + when checking the restriction caps, not adding proper check, which + results in assertion error when calling gst_caps_from_string + https://bugzilla.gnome.org/show_bug.cgi?id=752749 + +2015-07-23 09:18:46 +0900 Vineeth TM + + * validate/launcher/main.py: + validate:launcher: Fix documentation + Fix some trivial spelling mistakes in documentation + and document about --update-media-info. + https://bugzilla.gnome.org/show_bug.cgi?id=752748 + +2015-07-22 08:45:26 +0900 Vineeth TM + + * validate/launcher/baseclasses.py: + validate:launcher: escape the characters to remove bad range in regex + When media file name consists of some special characters of the format + [b-a].mp3, then it fails with 'bad character range' error and exits. + call re.escape to escape the characters before using it in findall + https://bugzilla.gnome.org/show_bug.cgi?id=752650 + +2015-08-04 08:35:16 +0900 Vineeth TM + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: improve uri generation for --medias-path + When --medias-paths option is being used, right now we have to + specify the full path, like /home/user/gst/master/media/ + But when inside master directory, would like to specify only + media/ and expect it to work. Using os.path.abspath and create uri based on that. + This way we can either just pass media/ or pass the full path as parameters. + https://bugzilla.gnome.org/show_bug.cgi?id=752518 + +2015-07-17 16:45:35 +0900 Vineeth TM + + * validate/launcher/baseclasses.py: + validate:launcher: skip setting up test suite for --medias-paths + in validate.py, some mixer test generators are being added by default. + When passing --media-paths, i would not want to test these. + So instead of setting up the validate test suite, just call tester.register_defaults(). + https://bugzilla.gnome.org/show_bug.cgi?id=752518 + +2015-08-04 13:47:24 +0900 Vineeth TM + + * validate/data/scenarios/reverse_playback.scenario: + validate: scenarios: Change start time for reverse playback + Right now reverse playback happens till the beginning of the media file. + But for files which are longer than 150 seconds, + Timeout 'Hard timeout reached: 150 secs' error happens. So we should set the + start time within 150 seconds. + https://bugzilla.gnome.org/show_bug.cgi?id=753216 + +2015-08-04 15:21:16 +0200 Thibault Saunier + + * validate/launcher/main.py: + validate: launcher: Add a --force-sync option + Which should put the testsuite in a clean state (basically using git + reset --hard for git based testsuite for example) + +2015-08-04 08:53:17 -0400 Nicolas Dufresne + + * validate/launcher/apps/gstvalidate.py: + validate: Enable flac reverse playback tests + This is now supported and works as expected. + +2015-07-31 10:50:24 -0400 Nicolas Dufresne + + * validate/gst/validate/media-descriptor.c: + validate: media-descriptor: Workaround file:// stream-id changing + file:// base stream-id will vary depending on the file path. As we + don't expect everyone to use the same absolute path to place the + validate testsuite, the resulting stream-id changes. Because of that, + we can't match the stream-id in the recorded file, hence cannot do + further check. We work around this by doing what filesink would do, + which is compute a SHA256 of the URI which we can use to first + validate the ID is prefixed like expected, and decide if we should + consider the stream IDs the same or not. + https://bugzilla.gnome.org/show_bug.cgi?id=753079 + +2015-07-31 10:49:00 -0400 Nicolas Dufresne + + * validate/gst/validate/media-descriptor-parser.c: + validate: media-descriptor: Fix reading seekable record + Casting the result of g_strmp0 to boolean won't make gboolean + value 0 or 1. We need proper 0 and 1 so we can use == comparision. + +2015-07-24 15:36:27 +0900 Vineeth TM + + * validate/gst/validate/media-descriptor.c: + * validate/tools/gst-validate-media-check.c: + validate: media-descriptor: handle proper return values + while comparing the media descriptor with --expected-results, the return + values are not being handled properly, which results in wrong comparision + https://bugzilla.gnome.org/show_bug.cgi?id=748390 + +2015-07-30 15:14:13 -0400 Nicolas Dufresne + + * validate/gst/validate/media-descriptor.c: + validate: media-descriptor: Add comment before ignored return value + As stated in the bug, this comparison failing is not a critical + error, warning is enough. Add a comment so nobody thinks it's a + coding error. + https://bugzilla.gnome.org/review?bug=748390 + +2015-07-22 16:32:06 +0900 Vineeth TM + + * validate/gst/validate/media-descriptor.c: + validate: media-descriptor: remove duplicate conditions + when comparing tags, two conditions in if an else if are same + the correct way is to first check if both are NULL and return. + changed the condition accordingly. + https://bugzilla.gnome.org/show_bug.cgi?id=748390 + +2015-07-22 16:07:19 +0900 Vineeth TM + + * validate/gst/validate/media-descriptor.c: + validate: media-descriptor: fix trivial spelling mistakes + replace comparse_stream with compare_streams + https://bugzilla.gnome.org/show_bug.cgi?id=748390 + +2015-07-29 16:42:48 +0900 Vineeth TM + + * validate/tools/gst-validate.c: + validate:tools: set locale to all and change argument to FILENAME + When file name consists of characters from other languages, say korean, + then it throws an error + Error initializing: Invalid byte sequence in conversion input + Hence setting locale to all to fix this. + And changing the media-info argument to type G_OPTION_ARG_FILENAME + https://bugzilla.gnome.org/show_bug.cgi?id=752945 + +2015-07-25 10:54:19 +0200 Thibault Saunier + + * validate/docs/validate/gst-validate-sections.txt: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate: Add a method to get action->scenario in a thread safe way + API: + gst_validate_action_get_scenario + +2015-07-24 16:47:57 -0400 Olivier Crête + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate-scenario: Use thread-safe GWeakRef + Since _set_done() is meant to be thread safe, + it can not be used with g_object_add_weak_pointer(), + instead, one must use GWeakRef. But since it is in the API, + document that fact and add a couple assertions to make sure + it doesn't get broken in the future. + +2015-07-24 16:25:38 -0400 Olivier Crête + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: Use GLib functions to make sure GMainContext is used + +2015-07-24 16:19:46 -0400 Olivier Crête + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Only modify the actions from the main thread + The action's content is not protected by a mutex, so only + modify it from the main thread. + +2015-07-24 17:05:30 -0400 Olivier Crête + + * .gitignore: + * codecanalyzer/.gitignore: + * validate/.gitignore: + * validate/docs/plugins/.gitignore: + gitignore: Add more generated files + +2015-07-17 23:42:22 +0900 Vineeth T M + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: get duration from media_info if not able to query + In case of files, which don't have duration in header, baseparse + estimates the duration only after 1.5 seconds. But Async_done event + is sent before the duration is estimated, which results in error. + If duration query fails, getting the duration from the media-info being + passed through --set-media-info. If media-info is also not set, + printing an error message and throwing error. + https://bugzilla.gnome.org/show_bug.cgi?id=752521 + +2015-07-20 19:37:41 +0900 Vineeth T M + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: add quotes to the file path + When folder name contains space or other special characters, + it fails to recognise the same and error is thrown. Adding the path + inside to recognise the same + https://bugzilla.gnome.org/show_bug.cgi?id=752611 + +2015-07-20 19:35:34 +0900 Vineeth T M + + * validate/launcher/utils.py: + validate:launcher: unquote the path to remove special characters + When getting path from url using, url2path, it is returning + special characters (%20 for space etc..), instead of returning + plain path. path.unquote replaces the same.. + https://bugzilla.gnome.org/show_bug.cgi?id=752611 + +2015-07-20 17:27:56 +0900 Vineeth TM + + * validate/tools/gst-validate-media-check.c: + validate:launcher: return on error cases properly + When folder name contains spaces during --medias-paths, it does not + create the media info, but still it shows as passed. + Returing failed during this case + https://bugzilla.gnome.org/show_bug.cgi?id=752611 + +2015-07-22 15:20:54 +0900 Vineeth TM + + * validate/launcher/apps/gstvalidate.py: + validate:launcher: handle file path in --medias-paths + right now --medias-paths accepts only directories. Added support to + accept file path as well. + https://bugzilla.gnome.org/show_bug.cgi?id=752692 + +2015-07-16 20:27:11 +0900 Vineeth T M + + * validate/launcher/main.py: + validate:launcher: skip default media path for --media-paths option + when --media-paths is specified, then no need to check the default media. + And add Force argument to let testsuite force the inclusion of + default media directory. + https://bugzilla.gnome.org/show_bug.cgi?id=752461 + +2015-07-16 13:44:07 +0200 Thibault Saunier + + * validate/gst-libs/gst/video/gstvalidatessim.c: + validate:ssim: Fix calls to the converters + We were mixing them + +2015-07-16 10:28:18 +0900 Vineeth T M + + * validate/launcher/main.py: + validate:main.py: trivial document fixes + gst-validate-launch is being used instead of gst-validate-launcher + in a couple of places. + https://bugzilla.gnome.org/show_bug.cgi?id=752455 + +2015-07-14 20:31:59 +0200 Thibault Saunier + + * validate/plugins/gtk/gstvalidategtk.c: + validate:gtk: Handle the case were we are 'pressing' only a modifier + +2015-07-14 18:28:18 +0200 Guillaume Desmottes + + * validate/gst/validate/gst-validate-scenario.c: + validate: don't store the full description struct + Summary: + When running valgrind we'll have 2 scenarios loaded (the normal one and + "setup_sink_props_max_lateness.scenario"). The loading code shouldn't assume + which one will contain the description it actually care about and so just look + for the fields it actually needs. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D199 + +2015-07-14 18:16:40 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Report EVENT_SEEK_NOT_HANDLED as reported error on error + +2015-07-13 13:10:15 +0200 Thibault Saunier + + * validate/docs/validate/envvariables.xml: + validate: Document the GST_VALIDATE_CONFIG environment variable + +2015-07-13 13:05:41 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Implement a config to set the interval between action calls + Allowing users to decide the time between which the action should be + executed. In some cases executing on idle might lead to action not + being executed fast enough so the user might want to force an interval + in that case. + +2015-06-24 17:43:53 +0200 Thibault Saunier + + * validate/configure.ac: + Back to development + +=== release 1.5.2 === + +2015-06-24 17:42:16 +0200 Thibault Saunier + + * validate/ChangeLog: + * validate/NEWS: + * validate/configure.ac: + * validate/gst-validate.doap: + Release 1.5.2 + +2015-06-24 16:06:06 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Reset seeked_in_paused when wrong position detected + + Lower some debug output to LOG + +2015-06-18 11:09:26 +0200 Thibault Saunier + + * validate/gst-libs/gst/video/gstvalidatessim.c: + * validate/plugins/ssim/gstvalidatessim.c: + * validate/tools/gst-validate-images-check.c: + validate:ssim: Inform about min average and min minimum similarities + +2015-06-14 22:44:26 +0100 Tim-Philipp Müller + + * validate/gst/validate/media-descriptor-writer.c: + * validate/tools/gst-validate-media-check.c: + validate: spelling fixes + analize != analyze + +2015-05-29 16:45:25 +0900 Wonchul Lee + + * validate/tools/gst-validate-media-check.c: + validate: media-check: add newline to end of print statements + https://bugzilla.gnome.org/show_bug.cgi?id=750089 + +2015-06-09 09:10:42 +0900 Wonchul Lee + + * validate/tools/gst-validate-media-check.c: + validate: media-check: fix double unref in error code path + Writer would get unrefed twice when it could not parse the file. + https://bugzilla.gnome.org/show_bug.cgi?id=750606 + +2015-06-13 19:25:17 +0100 Tim-Philipp Müller + + * validate/tools/Makefile.am: + validate: tools: fix build + /usr/bin/ld: gst-validate-images-check.o: undefined reference to symbol 'gst_init' + /home/tpm/gst/glib-master/gstreamer/gst/.libs/libgstreamer-1.0.so.0: error adding symbols: DSO missing from command line + +2015-06-12 12:10:55 +0200 Thibault Saunier + + * validate/gst-libs/gst/video/gstvalidatessim.c: + * validate/plugins/ssim/gstvalidatessim.c: + validate:ssim: Make position reporting parseable by the launcher + +2015-06-12 11:00:54 +0200 Thibault Saunier + + * validate/plugins/ssim/gstvalidatessim.c: + validate:ssim: Print better information about execution + +2015-06-12 10:59:28 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + validate: print REPORTER->name when passed as source in validate_printf + +2015-06-12 11:17:43 +0200 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Avoid printing twice env variables + When printing test command. + +2015-06-09 10:52:21 +0200 Thibault Saunier + + * validate/tools/Makefile.am: + validate:tools: Cleanup Makefile.am + Removing useless CFLAGS and LIBS + +2015-06-09 11:14:58 +0900 Wonchul Lee + + * validate/gst/validate/media-descriptor-writer.c: + validate:media-descriptor-writer: cleanup get tag code + https://bugzilla.gnome.org/show_bug.cgi?id=750609 + +2015-06-08 18:48:30 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-override.h: + validate: Do not define GstValidateOverride type twice + +2015-06-08 18:20:33 +0200 Thibault Saunier + + * validate/Makefile.am: + * validate/data/Makefile.am: + * validate/docs/plugins/Makefile.am: + * validate/docs/plugins/gst-validate-plugins-overrides.txt: + * validate/tools/Makefile.am: + * validate/tools/gst-validate-images-check.c: + validate: Fix make distcheck + +2015-06-08 17:11:51 +0200 Thibault Saunier + + * validate/configure.ac: + * validate/docs/Makefile.am: + * validate/docs/plugins/Makefile.am: + * validate/docs/plugins/gst-validate-plugins-docs.sgml: + * validate/docs/plugins/gst-validate-plugins-sections.txt: + * validate/docs/plugins/gst-validate-plugins.sgml: + * validate/docs/plugins/gst-validate-plugins.types: + * validate/docs/version.entities: + * validate/docs/version.entities.in: + * validate/plugins/Makefile.am: + validate: Generate documentation for Validate plugins + Summary: Depends on D215 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D216 + +2015-06-08 17:10:50 +0200 Thibault Saunier + + * validate/Makefile.am: + * validate/configure.ac: + * validate/plugins/ssim/Makefile.am: + * validate/plugins/ssim/gstvalidatessim.c: + validate: Add a validate ssim plugin + Summary: + + Bump gst-video dependency to 1.4 as we need GstVideoConvert + Depends on D213: validate: Mark gst_validate_report a G_GNUC_PRINTF + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D215 + +2015-06-03 12:43:52 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-reporter.h: + * validate/gst/validate/media-descriptor.c: + validate: Mark gst_validate_report a G_GNUC_PRINTF + Summary: + And fix the issue it raised + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D213 + Depends on D211 + +2015-05-25 13:41:04 +0200 Thibault Saunier + + * validate/Makefile.am: + * validate/configure.ac: + * validate/gst-libs/Makefile.am: + * validate/gst-libs/gst/Makefile.am: + * validate/gst-libs/gst/video/Makefile.am: + * validate/gst-libs/gst/video/gssim.c: + * validate/gst-libs/gst/video/gssim.h: + * validate/gst-libs/gst/video/gstvalidatessim.c: + * validate/gst-libs/gst/video/gstvalidatessim.h: + * validate/tools/.gitignore: + * validate/tools/Makefile.am: + * validate/tools/gst-validate-images-check.c: + validate: Add a gst-validate-images-check tool + Summary: + That is a new tool that uses ssim algorithm to compare images + + Add a GstValidateVideo internal library adding an helper Gssim class + Depends on D210 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D211 + +2015-05-27 19:35:15 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/gst-validate-utils.h: + validate:utils: Add a utility to get a GstClockTime from a structure + Summary: + Properly handling the different types that can represent ClockTime + Make use of it in gst_validate_action_get_clocktime + API: gst_validate_utils_get_clocktime + Depends on D209 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D210 + +2015-05-26 18:45:45 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/validate.c: + validate: Properly clear the overrides registry on deinit + Summary: Depends on D208 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D209 + +2015-05-26 13:58:15 +0200 Thibault Saunier + + * validate/gst/validate/validate.c: + * validate/gst/validate/validate.h: + validate: Add a way to check whether Validate is initialized + Summary: + API: + gst_validate_is_initialized + Depends on D207 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D208 + +2015-05-26 15:57:29 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-override.c: + * validate/gst/validate/gst-validate-override.h: + validate:override: Add a vmethod to check whether a monitor can attach it + Summary: Depends on D206 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D207 + +2015-05-26 12:04:02 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-override-registry.c: + validate:override-registry: Make use of gst_validate_element_has_klass + Summary: + + Fix a minor mixup bug between klass_overrides and name_overrides + Depends on D205 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D206 + +2015-05-26 12:03:25 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-override.c: + * validate/gst/validate/gst-validate-override.h: + * validate/gst/validate/gst-validate-reporter.c: + validate:override: Make overrides GObjects + Summary: + This way we can subclass them getting a proper + context in the various override methods. + Depends on D204 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D205 + +2015-05-25 18:52:34 +0200 Thibault Saunier + + * validate/Makefile.am: + * validate/configure.ac: + * validate/gst/Makefile.am: + * validate/plugins/Makefile.am: + * validate/plugins/fault_injection/Makefile.am: + * validate/plugins/fault_injection/socket_interposer.c: + * validate/plugins/gapplication/Makefile.am: + * validate/plugins/gapplication/gstvalidategapplication.c: + * validate/plugins/gtk/Makefile.am: + * validate/plugins/gtk/gstvalidategtk.c: + validate: Move plugins to the toplevel directory + Summary: + Otherwise we end up with circular / complicated dependencies between + Validate, its libraries, and the plugins + Depends on D203 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D204 + +2015-05-27 16:41:00 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Round up computed ClockTime values + Otherwise we end up with rounding error and instead of + seeking to 0.1 we seek to 0.09999999999 for example + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D203 + +2015-05-27 13:18:33 +0200 Thibault Saunier + + * validate/gst/preload/gst-validate-monitor-preload.c: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-runner.h: + * validate/tools/gst-validate-media-check.c: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate:runner: Add a method to force exiting the runner + This method is similar to runner_printf() but can be used + only once. The user needs to make sure all the pipeline + are in NULL state when this is called. + The method emits a "STOPPING" signal and at that point + overrides or monitors should do extra processing/checks if + needed. + + Make use of it everywhere where it makes sense. + API: + gst_validate_runner_exit + GstValidateRunner::stopping signal + +2015-06-02 20:25:56 -0400 Olivier Crête + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/gst/validate/gst-validate-pipeline-monitor.h: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/tests/check/validate/padmonitor.c: + pad-monitor: Check that an ERROR GstMessage has been posted on GST_FLOW_ERROR + Summary: + Before returning GST_FLOW_ERROR, an element must post an ERROR GstMessage, + enforce that. + Reviewers: thiblahute, Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D201 + +2015-06-03 11:49:58 +0100 Luis de Bethencourt + + * validate/gst/validate/media-descriptor-writer.c: + validate: remove unused assignment + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D202 + +2015-06-02 16:46:15 -0400 Olivier Crête + + * validate/gst/validate/gst-validate-utils.c: + Revert "validate-utils: simplify _read_builtin ()" + This breaks the fast_forward scenario parsing. + This reverts commit 0cfff156b1d7013174652cdd25d3ad3f0571813e. + +2015-05-29 17:40:26 +0100 Luis de Bethencourt + + * validate/gst/validate/gst-validate-utils.c: + validate-utils: clean error handling in _file_get_lines () + +2015-05-29 16:29:44 +0100 Luis de Bethencourt + + * validate/gst/validate/gst-validate-utils.c: + validate-utils: simplify _read_builtin () + +2015-05-29 15:40:04 +0100 Vineeth T M + + * validate/gst/validate/gst-validate-utils.c: + validate-utils: sqrt(-1.0) leads to undefined result + Using sqrt of -1 is not valid and leads to undefined results. + When comparing the return value of the fucntion in validate-scenario, + it is being checked with ret == -1, so it makes sense to just return -1 in error case. + https://bugzilla.gnome.org/show_bug.cgi?id=748389 + +2015-05-20 13:57:55 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Make sure to actually test position to execute actions + +2015-05-15 14:45:04 +0200 Guillaume Desmottes + + * validate/data/gstvalidate.supp: + validate: add valgrind ignore supps for theora encoder + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D181 + +2015-05-15 14:26:35 +0200 Guillaume Desmottes + + * validate/data/gstvalidate.supp: + validate: add more H264 valgrind supp rules + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D180 + +2015-05-15 12:57:49 +0200 Thibault Saunier + + * validate/gst/plugins/gtk/gstvalidategtk.c: + validate:gtk: Use event->type directly + gdk_event_get_event_type was introduced in Gtk 3.10 only + https://bugzilla.gnome.org/show_bug.cgi?id=749421 + +2015-05-14 17:43:40 +0200 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: extra_env_variables is a dictionnary + +2015-05-13 15:30:23 +0200 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Always set the protocol when creating a GstValidateMediaDescriptor + Summary: Depends on D174 + Reviewers: Mathieu_Du, gdesmott + Differential Revision: http://phabricator.freedesktop.org/D175 + +2015-05-13 15:29:43 +0200 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + validate:launcher: Allow passing extra env variables to the tests + Summary: Depends on D173 + Reviewers: Mathieu_Du, gdesmott + Differential Revision: http://phabricator.freedesktop.org/D174 + +2015-05-13 15:27:08 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-override-registry.c: + validate: overrides: Allow using regex for named overrides + Summary: + And minor fixes + Depends on D172 + Reviewers: Mathieu_Du, gdesmott + Differential Revision: http://phabricator.freedesktop.org/D173 + +2015-05-13 12:18:18 +0200 Thibault Saunier + + * validate/gst/plugins/gapplication/gstvalidategapplication.c: + * validate/gst/plugins/gtk/gstvalidategtk.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario: Handle action execution after pipeline destruction + Summary: + It is possible to keep executing actions after the pipeline + has been destroyed. + API: + GST_VALIDATE_ACTION_TYPE_DOESNT_NEED_PIPELINE + Depends on D171 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D172 + +2015-05-13 12:16:57 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario: Add a macro to get ActionType from an Action + Summary: Depends on D170 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D171 + +2015-05-13 12:13:17 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Factor out code to check position + Summary: + Making simpler to follow the execute_next_action function. + Depends on D169 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D170 + +2015-05-13 11:27:25 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + validate:pipeline-monitor: Stop printing position when not possible + Summary: + If from anything >= PAUSED to anything <= READY we can not query + pipeline position, so do not try to. + Depends on D168 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D169 + +2015-05-13 11:20:42 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Rename 'get_position_id' to 'execute_actions_source_id' + Summary: Depends on D167 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D168 + +2015-05-12 12:07:13 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Do not unref twice the same list + Summary: Depends on D166 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D167 + +2015-05-12 10:58:19 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Add a way to specify the pipeline on which a scenario applies + Summary: + From within the scenario itself. + Depends on D165 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D166 + +2015-05-12 12:04:52 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Avoid depending on Gst 1.4 + Summary: Depends on D117 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D165 + +2015-04-21 15:29:15 +0200 Thibault Saunier + + * validate/configure.ac: + * validate/gst/plugins/Makefile.am: + * validate/gst/plugins/gtk/Makefile.am: + * validate/gst/plugins/gtk/gstvalidategtk.c: + validate: Add a gtk plugins that implements action types relative to Gtk + Summary: + Currently the only supported action is gtk-put-event allowing press and + release keyboard keys. + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D117 + +2015-05-12 09:55:58 +0200 Thibault Saunier + + * validate/autogen.sh: + validate: Always git submodule update from the toplevel directory + Otherwise it fails with older git versions + +2015-05-09 16:28:20 +0200 Emanuele Aina + + * validate/docs/validate/Makefile.am: + * validate/docs/validate/command-line-tools.xml: + * validate/docs/validate/envvariables.xml: + * validate/docs/validate/gst-validate-docs.sgml: + * validate/docs/validate/gst-validate-launcher.xml: + * validate/docs/validate/gst-validate-media-check.xml: + * validate/docs/validate/gst-validate-transcoding.xml: + * validate/docs/validate/gst-validate.xml: + * validate/docs/validate/scenarios.xml: + validate: Reshape documentation + Fix some errors, use more Docbook tags and split each command reference + in its own file. + https://bugzilla.gnome.org/show_bug.cgi?id=749162 + +2015-05-09 16:23:06 +0200 Emanuele Aina + + * validate/autogen.sh: + validate: Go back to the validate dir after submodule init + https://bugzilla.gnome.org/show_bug.cgi?id=749162 + +2015-05-11 17:08:37 +0200 Guillaume Desmottes + + * validate/data/gstvalidate.supp: + validate: ignore x264 valgrind errors + Summary: The x264 code is pretty hardcore so I just opened a bug for now. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D164 + +2015-05-08 16:33:50 +0200 Guillaume Desmottes + + * validate/data/Makefile.am: + * validate/data/valgrind.config: + * validate/gst/validate/gst-validate-element-monitor.c: + * validate/launcher/baseclasses.py: + validate: disable QOS features when running with valgrind + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D156 + +2015-05-11 14:24:32 +0200 Guillaume Desmottes + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/gst-validate-utils.h: + validate: move element_has_klass() to utils + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D163 + +2015-05-11 12:22:25 +0200 Guillaume Desmottes + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + * validate/launcher/utils.py: + validate: rename get_valgrind_suppression_file() + Summary: + This function is actually not specific to valgrind so we can make it more + generic. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D162 + +2015-05-11 12:01:56 +0200 Guillaume Desmottes + + * validate/gst/validate/validate.c: + validate: allow to pass more than one file to GST_VALIDATE_CONFIG + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D161 + +2015-05-11 11:47:47 +0200 Guillaume Desmottes + + * validate/gst/validate/validate.c: + validate: gst_validate_plugin_get_config() return 'core' conf if plugin is NULL + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D160 + +2015-05-11 13:54:15 +0200 Guillaume Desmottes + + * validate/gst/validate/validate.c: + * validate/gst/validate/validate.h: + * validate/tests/check/validate/monitoring.c: + * validate/tests/check/validate/overrides.c: + * validate/tests/check/validate/padmonitor.c: + * validate/tests/check/validate/reporting.c: + * validate/tools/gst-validate-media-check.c: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: add gst_validate_deinit() + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D159 + +2015-05-11 14:25:49 +0200 Guillaume Desmottes + + * validate/gst/validate/validate.c: + validate: don't leak not maching config structures + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D158 + +2015-05-11 11:08:36 +0200 Guillaume Desmottes + + * validate/gst/validate/validate.c: + validate: factor out create_config() + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D157 + +2015-05-08 16:28:11 +0200 Guillaume Desmottes + + * validate/gst/validate/gst-validate-scenario.c: + validate: display debug info when stopping because EOS + Summary: + Useful to know if we are executing the 'stop' command provided by the scenario + or not. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D155 + +2015-05-11 19:40:49 +0200 Thibault Saunier + + * .gitignore: + Update .gitignore + +2015-05-07 11:19:57 +0200 Guillaume Desmottes + + * validate/gst/validate/gst-validate-pad-monitor.c: + validate: unref last_caps when destroying pad monitor + Reviewers: thiblahute + Reviewed By: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D150 + +2015-05-05 15:59:18 +0200 Guillaume Desmottes + + * validate/data/scenarios/reverse_playback.scenario: + validate: fix typo in reverse_playback.scenario + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D148 + +2015-05-05 12:46:38 +0200 Guillaume Desmottes + + * validate/gst/validate/gst-validate-scenario.c: + validate: initialize position + Summary: Fix invalid read when executing without having the actual position. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D147 + +2015-05-05 09:32:53 +0200 Guillaume Desmottes + + * validate/data/gstvalidate.supp: + validate: be less specific when ignoring the pixman tls leak + Summary: I hit the same big in a slightly different code path. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D146 + +2015-05-04 14:22:00 +0200 Guillaume Desmottes + + * validate/.gitignore: + * validate/tools/.gitignore: + update gitignore + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D145 + +2015-04-30 17:39:55 +0200 Guillaume Desmottes + + * validate/data/scenarios/setup_sink_props_max_lateness.scenario: + * validate/launcher/baseclasses.py: + use the setup_sink_props_max_lateness config scenario with valgrind + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D141 + +2015-04-30 17:22:19 +0200 Guillaume Desmottes + + * validate/gst/validate/gst-validate-scenario.c: + validate: add 'target-element-klass' property on set-property action + Summary: + This allows us to set a property on all the elements of the pipeline matching + a specific klass name. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D140 + +2015-05-01 16:39:04 +0200 Guillaume Desmottes + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate: add 'optional' action keyword + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D139 + +2015-04-30 15:39:23 +0200 Guillaume Desmottes + + * validate/gst/validate/gst-validate-scenario.c: + validate: scenario: call _element_added_cb() on existing children + Summary: + We want to have a chance to set property on all the elements of the pipelines, + including the existing children when the element is added. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D138 + +2015-04-29 14:12:01 +0200 Guillaume Desmottes + + * validate/gst/validate/gst-validate-report.c: + * validate/launcher/httpserver.py: + * validate/launcher/vfb_server.py: + Fix 'stoped' typo + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D137 + +2015-04-27 15:57:13 +0200 Guillaume Desmottes + + * validate/data/gstvalidate.supp: + validate: add vg suppression for libdrm bug + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D134 + +2015-04-27 15:14:10 +0200 Guillaume Desmottes + + * validate/data/gstvalidate.supp: + validate: ignore invalid read from libav aac decoding + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D133 + +2015-04-27 14:48:54 +0200 Guillaume Desmottes + + * validate/data/gstvalidate.supp: + validate: ignore libvpx valgrind errors + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D132 + +2015-04-27 14:04:05 +0200 Guillaume Desmottes + + * validate/data/gstvalidate.supp: + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + validate: display the URL of ignored Valgrind bugs + Summary: + We don't want to forget about those so best to remind it when starting tests + as we do with blacklisted tests. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D131 + +2015-04-27 13:25:44 +0200 Guillaume Desmottes + + * validate/launcher/baseclasses.py: + * validate/launcher/utils.py: + validate: move look_for_file_in_source_dir and get_valgrind_suppression_file to utils + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D130 + +2015-04-30 23:57:09 +0200 Thibault Saunier + + * validate/autogen.sh: + validate: Make sure to run submodule init from the root dir + +2015-04-29 13:22:11 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Fix 'duration' property of the pause action + We preparse it into and set it as GstClockTime in the + structures so make sure to use them as such. + +2015-04-28 16:44:42 +0200 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Concider unset MediaDescriptor duration has 'infinite' + +2015-04-22 11:38:56 +0200 Guillaume Desmottes + + * validate/data/gstvalidate.supp: + validate: ignore a pixman leak which is fixed in master + http://phabricator.freedesktop.org/D128 + +2015-04-21 15:57:57 +0200 Guillaume Desmottes + + * validate/data/gstvalidate.supp: + validate: use a bigger hammer to ignore mesa related leaks + Looks like some tests are hitting a slightly different code path in udev but + the root bug is the same. + http://phabricator.freedesktop.org/D128 + +2015-04-23 12:33:26 +0100 Tim-Philipp Müller + + * codecanalyzer/src/codecanalyzer.c: + codecanalyzer: minor style fix + +2015-04-23 15:53:12 +0900 Vineeth T M + + * codecanalyzer/src/codecanalyzer.c: + codecanalyzer: don't try to free uninitialized pointers + xml_files_path and hex_files_path variable are not initialized. + There are chances that corruption happens when uninitialized + variables are freed, so init them to NULL before use. + https://bugzilla.gnome.org/show_bug.cgi?id=748351 + +2015-04-23 12:23:24 +0100 Tim-Philipp Müller + + * codecanalyzer/src/codecanalyzer.c: + codecanalyzer: run gst-indent on code + +2015-04-23 11:44:24 +0200 Thibault Saunier + + * validate/autogen.sh: + * validate/gst-validate.doap: + * validate/po/Makevars: + validate: Update autogen.sh + And add a gst-validate.doap file. + +2015-04-23 11:24:14 +0200 Thibault Saunier + + * validate/common: + Update common submodule + +2015-04-21 11:00:58 +0200 Guillaume Desmottes + + * validate/launcher/baseclasses.py: + * validate/tools/Makefile.am: + validate: use -debug versions of bins when running from source + Summary: + Those versions are using rpath instead of libtool's wrappers and so will be + faster to start and won't confuse valgrind. + Reviewers: thiblahute + Differential Revision: http://phabricator.freedesktop.org/D116 + +2015-04-20 15:24:46 +0200 Guillaume Desmottes + + * validate/configure.ac: + * validate/data/Makefile.am: + * validate/data/scenarios/Makefile.am: + * validate/data/scenarios/adaptive_video_framerate.scenario: + * validate/data/scenarios/adaptive_video_framerate_size.scenario: + * validate/data/scenarios/adaptive_video_size.scenario: + * validate/data/scenarios/alternate_fast_backward_forward.scenario: + * validate/data/scenarios/camerabin_signal.scenario: + * validate/data/scenarios/change_state_intensive.scenario: + * validate/data/scenarios/disable_subtitle_track_while_paused.scenario: + * validate/data/scenarios/fast_backward.scenario: + * validate/data/scenarios/fast_forward.scenario: + * validate/data/scenarios/force_key_unit.scenario: + * validate/data/scenarios/pause_resume.scenario: + * validate/data/scenarios/play_15s.scenario: + * validate/data/scenarios/reverse_playback.scenario: + * validate/data/scenarios/scrub_backward_seeking.scenario: + * validate/data/scenarios/scrub_backward_seeking_full.scenario: + * validate/data/scenarios/scrub_forward_seeking.scenario: + * validate/data/scenarios/scrub_forward_seeking_full.scenario: + * validate/data/scenarios/seek_backward.scenario: + * validate/data/scenarios/seek_forward.scenario: + * validate/data/scenarios/seek_forward_backward.scenario: + * validate/data/scenarios/seek_with_stop.scenario: + * validate/data/scenarios/simple_seeks.scenario: + * validate/data/scenarios/switch_audio_track.scenario: + * validate/data/scenarios/switch_audio_track_while_paused.scenario: + * validate/data/scenarios/switch_set_external_subtitle.scenario: + * validate/data/scenarios/switch_subtitle_track.scenario: + * validate/data/scenarios/switch_subtitle_track_while_paused.scenario: + * validate/data/scenarios/update_start.scenario: + * validate/data/scenarios/update_stop.scenario: + * validate/gst/validate/gst-validate-scenario.c: + move scenarios to data/scenarios + Differential Revision: http://phabricator.freedesktop.org/D115 + +2015-04-20 10:53:29 +0200 Guillaume Desmottes + + * validate/Makefile.am: + * validate/data/gstvalidate.supp: + * validate/launcher/baseclasses.py: + validate: add gstvalidate.supp valgrind suppression file + Differential Revision: http://phabricator.freedesktop.org/D115 + +2015-04-13 13:55:56 +0200 Guillaume Desmottes + + * validate/gst/validate/gst-validate-scenario.c: + validate: scenario: fix structure and action leak + +2015-04-10 11:28:34 +0200 Guillaume Desmottes + + * validate/gst/validate/gst-validate-report.c: + validate: report: fix GString leak when early returning + +2015-04-19 11:57:36 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + validate:pipelinemonitor: Print position only when in state >= PAUSED + Reviewers: Mathieu_Du + Reviewed By: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D86 + +2015-04-19 11:56:29 +0200 Thibault Saunier + + * validate/launcher/vfb_server.py: + validate:launcher: Use full HD screen as default screen size in xvfb + +2015-04-17 20:37:21 +0200 Thibault Saunier + + * validate/launcher/Makefile.am: + * validate/launcher/main.py: + * validate/launcher/vfb_server.py: + validate:launcher: Add a way to run tests without displaying the output + Summary: + Adding a --no-display option and running Xvfb virtual frame buffer X + server. + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D105 + +2015-04-17 19:56:17 +0200 Thibault Saunier + + * validate/launcher/main.py: + validate:launcher: Error out if valgrind is not available on the system + Summary: + When the user wants to use valgrind, make sure it is present on the + system before doing anything + Reviewers: gdesmott + Differential Revision: http://phabricator.freedesktop.org/D104 + +2015-04-17 19:28:19 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Stop scenario execution on stop action + And document it properly. + Summary: + The stop action was defined as "setting state to NULL" but + its actual goal is to stop the execution of the scenario. Make sure + that the scenario will not try to execute other actions when that + one has been executed. + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D103 + +2015-04-16 13:40:08 +0200 Thibault Saunier + + * validate/gst/validate/media-descriptor-writer.c: + * validate/gst/validate/media-descriptor.c: + validate: Gracefully handle absence of TAG on streams + Summary: And do not segfault when it happens! + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D99 + +2015-04-16 12:02:11 +0200 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Pass GST_VALIDATE_SCENARIO to the subprocess env only + Summary: + And make sure to remove it from the env if the user has it in its main + environment. + Without that commit we ended up passing scenarios from previous tests + to the following ones where None were specified. + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D98 + +2015-03-31 15:10:11 +0200 Guillaume Desmottes + + * validate/gst/validate/media-descriptor-writer.c: + validate: don't pass NULL to gst_caps_copy() + +2015-03-31 14:54:28 +0200 Guillaume Desmottes + + * validate/gst/validate/media-descriptor-writer.c: + * validate/gst/validate/media-descriptor-writer.h: + * validate/tools/gst-validate-media-check.c: + validate: use GstMediaDescriptorWriter as log handler + Allow us to catch warnings when running gst-validate-media-check-1.0. + +2015-03-31 09:59:58 +0200 Guillaume Desmottes + + * validate/gst/validate/media-descriptor-writer.c: + validate: media-descriptor-writer: fix string leaks + +2015-04-15 14:02:32 +0900 Young Han Lee + + * validate/tools/gst-validate-launcher.in: + validate:launcher: Handle git error properly + 'OSError' exception is emitted but not handled properly when git is not + installed on running system. + https://bugzilla.gnome.org/show_bug.cgi?id=747892 + +2015-04-14 12:31:32 +0200 Guillaume Desmottes + + * validate/launcher/baseclasses.py: + validate: set GST_GL_XINITTHREADS + This ensure that XInitThreads is called and so gl contexts are properly + initialized. + https://bugzilla.gnome.org/show_bug.cgi?id=747840 + Signed-off-by: Guillaume Desmottes + +2015-04-10 18:19:40 +0200 Thibault Saunier + + * validate/launcher/main.py: + validate:launcher: Make validate the only default testsuite + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D93 + +2015-04-10 18:11:09 +0200 Thibault Saunier + + * validate/launcher/main.py: + validate:launcher: Let the responsibility to update asset to the testsuite + Summary: + It makes it easier to make sure that the assets needed for a specific + testsuite are available when needed + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D92 + +2015-04-10 13:29:47 +0200 Thibault Saunier + + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate:tools: EOS handling is the responsibility of the scenario + Summary: If any scenario set + Reviewers: Mathieu_Du + Differential Revision: + http://phabricator.freedesktop.org/D90 + +2015-02-13 18:34:04 +0100 Ramiro Polla + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Add support for waiting on signals and messages + Reviewers: Mathieu_Du + Differential Revision: + http://phabricator.freedesktop.org/D88 + +2015-03-06 11:55:09 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario: Report disabling plugin issues + Summary: + + typedef GstValidateActionReturn so it can be used in the introspection + + Add GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED which should be used + to tell Validate that something wrong happened so the sub action + won't be executed, but that it should not report an error itself + as it has already been handled in the action function. + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D81 + +2015-03-06 11:51:19 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Allow execution of disable-plugin as a config action + Summary: + And fix a bug where config actions were added to the list of action even + if they had already been executed + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D80 + +2015-03-03 09:16:20 +0000 Thibault Saunier + + * validate/data/seek_forward_backward.scenario: + validate:scenarios: Set seek_forward_backward min-media-duration=45 + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D79 + +2015-02-27 23:20:43 +0000 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + validate:launcher: Set more env variable in the launcher command desc + Summary: + Adding if present: + * LD_PRELOAD + * DISPLAY + * GST_VALIDATE_CONFIG + * GST_VALIDATE_OVERRIDE + + enhance the add_env_variable method to more easily set envvar from + current value + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D78 + +2015-03-02 11:03:08 +0100 Thibault Saunier + + * validate/gst/plugins/gapplication/gstvalidategapplication.c: + * validate/gst/validate/validate.c: + * validate/gst/validate/validate.h: + validate: Add a method to easily get plugin configuration + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D77 + +2015-02-26 13:11:51 +0100 Thibault Saunier + + * validate/configure.ac: + * validate/gst/plugins/Makefile.am: + * validate/gst/plugins/gapplication/Makefile.am: + * validate/gst/plugins/gapplication/gstvalidategapplication.c: + validate:plugins: Add support to all GApplication as a test apps + Summary: + Add a very simple plugin that will allow any GApplication to easily be + used with GstValidate using the LD_PRELOAD feature + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D75 + +2015-01-17 22:21:16 +0100 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/gst-validate-utils.h: + validate: let structs_from_filename be exported. + Summary: It is useful for plugins too + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D74 + +2015-03-03 15:42:06 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Update Action.repeat field when needed + Summary: And print the current repeat value of the action that have such a field + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D73 + +2015-02-26 15:21:01 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Alway execute a 'quit' action on EOS + Summary: Making scenario more usable with LD_PRELOAD + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D72 + +2015-02-27 22:39:42 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Set the main action structure in fill_structure + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D71 + +2015-02-27 13:18:04 +0000 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Use GST_VALIDATE_SCENARIO envvar to set scenarios + Summary: + Instead of concidering all apps will have a --set-scenario argument + which is not going to be the case as soon as we run the tests through + LD_PRELOAD + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D70 + +2015-02-27 13:16:01 +0000 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + validate:launcher: Move get_current_position from GstValidatePipelineTest to GstValidateTest + This is where it belongs + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D69 + +2015-04-08 14:13:11 +0900 Wonchul Lee + + * validate/docs/validate/scenarios.xml: + validate: fix typo in scenario file format docs + https://bugzilla.gnome.org/show_bug.cgi?id=747487 + +2015-03-30 16:47:28 +0200 Guillaume Desmottes + + * validate/gst/validate/gst-validate-scenario.c: + validate: scenario: fix caps leak + +2015-03-30 16:46:12 +0200 Guillaume Desmottes + + * validate/gst/validate/media-descriptor.c: + validate: media-descriptor: fix filenode->caps leak + +2015-03-27 16:00:50 +0100 Guillaume Desmottes + + * validate/tools/gst-validate-transcoding.c: + validate: transcoding: don't create a second mainloop + +2015-03-27 16:00:19 +0100 Guillaume Desmottes + + * validate/tools/gst-validate-transcoding.c: + validate: transcoding: don't leak the requested sinkpad from decodebin + +2015-03-27 15:59:42 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-pad-monitor.c: + validate: pad-monitor: fix caps leak + Don't create othercaps when early returning. + +2015-03-27 12:16:03 +0100 Guillaume Desmottes + + * validate/launcher/apps/gstvalidate.py: + validate: GstValidateMediaCheckTest should inherit from GstValidateTest + +2015-03-31 09:20:05 +0900 Young Han Lee + + * validate/launcher/Makefile.am: + validate:launcher: Install config.py for non-development mode + Running installed gst-validate-launcher aborted with the following error. + File "lib/gst-validate-launcher/python/launcher/baseclasses.py", line 28, in + import config + ImportError: No module named config + This is because config.py is added but not installed + in ba6d209b3fd062f4e6bd889f81f1213cc12339ec. + https://bugzilla.gnome.org/show_bug.cgi?id=747087 + +2015-03-29 11:13:01 +0900 Young Han Lee + + * validate/launcher/baseclasses.py: + validate:launcher: Show timeout seconds for timeout result message + Current timeout message doesn't show how many seconds a test took and + it is timeouted by normal timeout or hard timeout. + This patch changes the message like following. + 1. normal timeout + old : validate.http.playback.reverse_playback.raw_video_mov: Timeout (Application timed out) + new : validate.http.playback.reverse_playback.raw_video_mov: Timeout (Application timed out: 120 secs) + 2. hard timeout + old : validate.http.playback.reverse_playback.raw_video_mov: Timeout (Application timed out) + new : validate.http.playback.reverse_playback.raw_video_mov: Timeout (Hard timeout reached: 600 secs) + https://bugzilla.gnome.org/show_bug.cgi?id=746957 + +2015-03-30 16:00:09 +0900 Young Han Lee + + * validate/launcher/baseclasses.py: + validate:launcher: Fix wrong test number with -j option + When '-j n' option is given, first n tests print test number 0. + This is caused by test_num part of 919db986052602dca452f05e284cfc857302d4f0. + https://bugzilla.gnome.org/show_bug.cgi?id=747006 + +2015-03-28 23:29:56 +0100 Thibault Saunier + + * validate/configure.ac: + * validate/launcher/baseclasses.py: + * validate/launcher/config.py.in: + validate:launcher: Avoid depending on PyGObject + Summary: + And rely on our knowledge of the configuration to figure out where the + suppression file has been installed + Reviewers: gdesmott + Differential Revision: http://phabricator.freedesktop.org/D61 + +2015-03-26 15:42:11 +0100 Guillaume Desmottes + + * validate/gst/validate/media-descriptor-writer.c: + validate: media-descriptor-writer: don't leak info and streaminfo + +2015-03-26 15:39:12 +0100 Guillaume Desmottes + + * validate/tools/gst-validate-media-check.c: + validate: media-check: don't leak output_file and expected_file + +2015-03-26 13:59:30 +0100 Guillaume Desmottes + + * validate/launcher/baseclasses.py: + validate: check VALGRIND_ERROR_CODE in Test as well + We were doing it only in GstValidateTest which was overriding the default + implementation. + +2015-03-26 13:57:34 +0100 Guillaume Desmottes + + * validate/launcher/baseclasses.py: + validate: don't increase hard_timeout is if it's None + Some tests, like the media check ones, have None as hard_timeout. + +2015-03-26 11:29:26 +0100 Guillaume Desmottes + + * validate/launcher/baseclasses.py: + validate: increase VALGRIND_TIMEOUT_FACTOR + 5 wasn't enough for my poor laptop. + +2015-03-26 11:29:06 +0100 Guillaume Desmottes + + * validate/launcher/baseclasses.py: + validate: increase the normal timeout as well when using valgrind + +2015-03-26 10:32:09 +0100 Guillaume Desmottes + + * validate/gst/validate/media-descriptor.c: + validate: media-descriptor: fix caps leak + gst_pad_get_current_caps() returns a reffed caps. + +2015-03-23 13:36:45 +0100 Guillaume Desmottes + + * validate/tools/gst-validate-media-check.c: + * validate/tools/gst-validate-transcoding.c: + also call gst_deinit() in media-check and transcoding + More valgrind friendly. + +2015-03-23 16:19:49 +0100 Guillaume Desmottes + + * validate/launcher/baseclasses.py: + validate: raise an error if valgrind detected issues + Differential Revision: http://phabricator.freedesktop.org/D53 + +2015-03-23 13:36:45 +0100 Guillaume Desmottes + + * validate/tools/gst-validate.c: + call gst_deinit() when we are done + More valgrind friendly. + +2015-03-23 13:35:41 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/media-descriptor-writer.c: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: call gst_bus_remove_signal_watch() + We are supposed to call gst_bus_remove_signal_watch() for each gst_bus_add_signal_watch() call to prevent leaks. + +2015-03-23 10:24:21 +0100 Guillaume Desmottes + + * validate/gst/validate/media-descriptor-parser.c: + validate: media-descriptor-parser: fix string leak + _set_content() doesn't actually consume @content so the caller is responsible + freeing it. + +2015-03-23 10:23:02 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-pad-monitor.c: + validate: pad-monitor: fix buffers list leak + +2015-03-23 10:22:47 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-pad-monitor.c: + validate: pad-monitor: fix caps leak + +2015-03-20 15:22:32 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-media-info.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/media-descriptor-writer.c: + validate: fix a bunch of GstBus leaks + +2015-03-23 09:39:30 +0100 Guillaume Desmottes + + * validate/launcher/baseclasses.py: + validate: store valgrind logs to its own file + +2015-03-20 15:00:28 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-scenario.c: + validate: report: fix invalid read when destroying Report + Summary: + @report was invalid when we were trying to clear the mutex. + validate: scenario: remove weak pointer when destroying action + Free an invalid read when the scenario is destroyed after the action. + Differential Revision: http://phabricator.freedesktop.org/D44 + +2015-03-20 14:49:24 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-report.c: + validate: report: fix invalid read when destroying Report + @report was invalid when we were trying to clear the mutex. + +2015-03-20 12:15:03 +0100 Guillaume Desmottes + + * validate/tools/gst-validate.c: + validate: fix string arguments leaks + We are responsible of freeing the string arguments parsed by GOptionContext. + +2015-03-20 11:39:32 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-scenario.c: + validate: scenario: don't borrow @structure in _fill_action() + @structure was borrowed in some code path and wasn't in some other. Make it + clearer, and fix a leak, by always copying it. + +2015-03-20 11:33:01 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/gst-validate-utils.c: + validate: override-registry: fix structs list leak + The list returned by _lines_get_strutures() needs to be deeply freed. + +2015-03-20 11:27:29 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-scenario.c: + validate: scenario: fix scenarios leak + +2015-03-20 11:25:39 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-reporter.c: + validate: reporter: fix message leak + +2015-03-20 11:24:27 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-report.c: + validate: report: don't shadow the GString variable + We were leaking the GString as it's freed outside of the block. + +2015-03-20 11:24:04 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-report.c: + validate: report: fix GStrv leak + +2015-03-20 11:23:29 +0100 Guillaume Desmottes + + * validate/gst/validate/gst-validate-report.c: + validate: report: fix GStrv leak + We borrow the content of the GStrv but were leaking the array itself. + +2015-03-19 17:22:26 +0100 Guillaume Desmottes + + * validate/launcher/baseclasses.py: + launcher: try using gst.supp as valgrind suppressions file + https://bugzilla.gnome.org/show_bug.cgi?id=746465 + +2015-03-19 17:44:19 +0100 Guillaume Desmottes + + * validate/Makefile.am: + validate: install gst.supp + Will be used when running tests inside Valgrind. + https://bugzilla.gnome.org/show_bug.cgi?id=746465 + +2015-03-19 16:06:54 +0100 Guillaume Desmottes + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + launcher: add valgrind support + Add a --valgrind option to gst-validate-launcher to run the tests inside + Valgrind and tune GLib's memory allocator accordingly. + Fix https://bugzilla.gnome.org/show_bug.cgi?id=746465 + +2015-03-20 10:06:35 +0100 Guillaume Desmottes + + * validate/data/Makefile.am: + * validate/docs/validate/envvariables.xml: + * validate/docs/validate/scenarios.xml: + * validate/gst/validate/gst-validate-scenario.c: + validate: move scenarios to validate/scenarios/ + https://bugzilla.gnome.org/show_bug.cgi?id=746465 + +2015-03-19 12:22:39 +0100 Guillaume Desmottes + + * validate/launcher/main.py: + validate:launcher: Fix small typo + +2015-03-18 17:05:19 +0100 Thibault Saunier + + * validate/launcher/main.py: + validate:launcher: Make sure to show apps specific options in the help + +2015-03-18 11:05:08 +0100 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Fix test number printing + +2015-03-14 15:40:17 +0000 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher Rename _other_testsuite_for_tester + To _check_tester_has_other_testsuite + +2015-03-14 15:08:12 +0000 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Cache all the tests in the runner + This way we do not have to re ask all the test managers + what tests should be run. + +2015-03-13 17:09:08 +0000 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + validate:launcher: Add a way to simply run validate default tests on uris + Summary: + This allows us to easily run all the scenarios on a particular file doing: + $ gst-validate-launcher validate --validate-check-uri file:///some/media/file.webm + Reviewers: Mathieu_Du + Differential Revision: http://phabricator.freedesktop.org/D36 + +2015-03-13 17:07:00 +0000 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: keep executing actions even after linking up following execution + When linking actions execution without waiting on execution context, then + idle callback should keep being called so following action keep being + executed. + +2015-03-10 10:29:28 +0100 Thibault Saunier + + * .arcconfig: + * validate/.gitignore: + validate: Add more files to .gitignore + Differential Revision: http://phabricator.freedesktop.org/D34 + +2015-03-10 10:25:23 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Rename get_position to execute_next_action + That function was wrongly called and did not correspond to what it + actually does. + +2015-03-09 18:26:37 +0000 Vincent Penquerc'h + + * validate/data/Makefile.am: + * validate/data/seek_backward_non_flushing.scenario: + * validate/data/seek_forward_non_flushing.scenario: + * validate/gst/validate/gst-validate-scenario.c: + Revert "validate: add non flushing seek support" + This reverts commit 3ff55dcc3119b39e7c86044159db8bce49a2dc3a. + Regressions on the test server, apparently linked to this patchset. + +2015-03-09 18:26:33 +0000 Vincent Penquerc'h + + * validate/gst/validate/gst-validate-scenario.c: + Revert "validate: use segments to detect success of flushing seeks too" + This reverts commit c47cc7ba90e96ffaefe201087428ef448670f3be. + Regressions on the test server, apparently linked to this patchset. + +2015-03-09 18:26:06 +0000 Vincent Penquerc'h + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-scenario.c: + Revert "validate: expect a buffer with discontinuity after a seek" + This reverts commit 87064b6994e36203b6976d436feda809068f1497. + Regressions on the test server, apparently linked to this patchset. + +2015-03-09 18:41:54 +0100 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Make sure TIMEOUTs do not get converted to ERROR + This was a regression introduced in c0e3d2e4f190fc9627897cc3d3d016448cb5dbe9 + +2015-02-27 16:56:06 +0000 Vincent Penquerc'h + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-scenario.c: + validate: expect a buffer with discontinuity after a seek + https://bugzilla.gnome.org/show_bug.cgi?id=744783 + +2015-02-27 14:40:09 +0000 Vincent Penquerc'h + + * validate/gst/validate/gst-validate-scenario.c: + validate: use segments to detect success of flushing seeks too + https://bugzilla.gnome.org/show_bug.cgi?id=744783 + +2015-02-19 13:12:50 +0000 Vincent Penquerc'h + + * validate/data/Makefile.am: + * validate/data/seek_backward_non_flushing.scenario: + * validate/data/seek_forward_non_flushing.scenario: + * validate/gst/validate/gst-validate-scenario.c: + validate: add non flushing seek support + and a couple scenarios using them + https://bugzilla.gnome.org/show_bug.cgi?id=744783 + +2015-03-06 09:39:10 +0100 Thibault Saunier + + * validate/launcher/apps/Makefile.am: + validate:launcher: Do not forget to install apps/__init__.py + It is a python module that should be usable by external apps/testsuites + +2015-03-05 13:33:27 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Handle not mandatory action types + Summary: + There is currently no way to handle the fact that action types + might be handled only by a specific application but not handling + this action types would not cause any difference for the good execution + of the scenario as a whole + Differential Revision: http://phabricator.freedesktop.org/D33 + +2015-03-02 17:32:56 +0100 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: Better handle GST debug log outputs redirection + +2015-03-04 17:30:41 +0100 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher: First rely on the presence of criticals to set tests result + In the case of external applications they might not set their exist + code bases on the result of validate so we should rely on what + validates as to say first. + +2015-03-04 17:26:55 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-reporter.c: + validate:report: Allow registering of issue types through the introspection + Fixing annotations and make GstValidateIssue refcounted + We break the ABI in that commit but I do not expect anyone to register + issue type outside GstValidate yet. + Add padding in the structures so we can avoid breaking the ABI again later. + +2015-03-04 17:24:52 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-monitor.c: + validate:monitor: Do not requiere a GstObject as target + We can work with any GObject and that allows applications to write + monitors for other aspects too + +2015-03-03 12:26:52 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Alway execute sub action on action running SYNC + Move methods around to avoid needing on top prototypes + +2015-03-03 19:26:33 +0900 Wonchul Lee + + * validate/docs/validate/command-line-tools.xml: + validate:docs: Rename gst-validate-launch to gst-validate-launcher + https://bugzilla.gnome.org/show_bug.cgi?id=745510 + +2015-03-03 11:33:06 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Do not execute last sub action twice when ASYNC + +2015-03-03 10:39:52 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Properly print sub action as if they were main actions + +2015-03-02 14:38:16 +0100 Emanuele Aina + + * validate/docs/validate/scenarios.xml: + validate:docs: Fix typos in Scenario File Format + https://bugzilla.gnome.org/show_bug.cgi?id=736160 + +2015-02-26 18:51:57 +0100 Thibault Saunier + + * validate/tools/gst-validate-launcher.in: + validate:launcher Do not use git -C as it is relatively recent + https://bugzilla.gnome.org/show_bug.cgi?id=736160 + +2015-02-26 11:09:23 +0100 Thibault Saunier + + * validate/gst/preload/Makefile.am: + validate: Build the preload so when possible + +2015-02-24 19:32:37 +0100 Thibault Saunier + + * validate/launcher/main.py: + validate:launcher: Print the long help in less when possible + +2015-02-24 19:08:12 +0100 Thibault Saunier + + * validate/launcher/main.py: + validate:launcher: Mention testsuite implementation in the help + +2015-02-23 12:24:39 +0100 Thibault Saunier + + * validate/configure.ac: + validate: Define GST_PLUGIN_LDFLAGS as needed + +2015-02-19 20:53:16 +0900 Young Han Lee + + * validate/tools/gst-validate-launcher.in: + validate: Determine development mode using git hash value + Development mode has been determined by whether the launcher is in git + repo + or not. This could be wrong when the launcher is installed to + subdirectory of other project's git repo, such as jhbuild. It is normal + to install compiled output to subdirectory of your jhbuild. + Changed logic gets the first commit hash of current git repo and + compares it with gst-devtools' the first commit hash. + https://bugzilla.gnome.org/show_bug.cgi?id=744781 + +2015-02-19 11:32:05 +0100 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + validate:launcher: Fix typo s/FILE_EXTENDION/FILE_EXTENSION/g + +2015-02-18 14:23:16 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario: Fix GstValidateAction ABI adding a private structure + This way we can easily extend the structure and avoid needing using + a union and such + +2015-01-22 22:29:10 +0100 Mathieu Duponchelle + + * validate/tests/check/validate/padmonitor.c: + validate: Test buffer outside of received range. + Summary: As part of the preparation for a port to tracer. + Test Plan: This is a test, we won't test tests + Reviewers: tsaunier + Differential Revision: http://internal.opencreed.com:8888/D19 + +2015-02-18 11:36:59 +0000 Tim-Philipp Müller + + * codecanalyzer/src/gst_analyzer.c: + codecanalyzer: fix codec detection with git master + The names might be 'MPEG-2 (Simple Profile)' now. + Shouldn't really rely on codec name strings here + in the first place, but use caps instead. + +2015-02-18 10:05:55 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-pipeline-monitor.c: + validate: Properly notify user about missing plugins + This way it is clear in gst-validate-launcher that the failure is due + to a missing plugin + +2015-02-17 18:18:56 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.h: + validate: Fix wrong sizeof usage + sizeof(int) is always <= sizeof(gpointer) + +2015-01-21 13:13:02 +0100 Ramiro Polla + + * validate/launcher/baseclasses.py: + validate: launcher: Use cElementTree for XML parsing + Using cElementTree instead of ElementTree speeds up parsing of media + descriptor files. + The total time spent parsing XML files drops from ~0.64 s to ~0.24 s, + leading to faster initialisation times for gst-validate-launcher. + https://bugzilla.gnome.org/show_bug.cgi?id=743293 + +2015-02-17 14:56:47 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate: Print actions directly from the scenario + Avoiding user to have to print them in each and every action type + implementation. + This requires adding some API to prepare actions before printing them. + Preparing action in that case mean parsing the values contained in the + GstStructure parsing equations and setting back the actual value + afterward + API: + * GstValidatePrepateAction + * gst_validate_action_type_set_prepare_function + +2015-02-16 22:12:54 +0100 Thibault Saunier + + * validate/gst/validate/validate.c: + validate: Fix build on windows + Check where libgstvalidate.dll is installed and use that base folder to + figure out where GstValidate plugins are installed + +2015-02-16 20:52:54 +0100 Thibault Saunier + + * validate/data/Makefile.am: + * validate/gst/validate/gst-validate-types.h: + validate: Do not forget to dist _full variant of scrubing scenarios + +2015-02-16 19:49:50 +0100 Thibault Saunier + + * validate/gst/validate/Makefile.am: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-scenario.h: + * validate/gst/validate/gst-validate-types.h: + * validate/gst/validate/validate.h: + validate: Create a gst-validate-types.h header where we define types + And include it from validate.h. + This way we avoid to need to typedef GstValidateAction twice, which is + a C11 feature + +2015-02-16 19:24:23 +0100 Edward Hervey + + * validate/gst/validate/gst-validate-report.c: + validate-report: Fix valist usage + a va_list always 'exists' (it's a struct). It therefore can't be NULL + (and can't be tested) + Just use the regular print variant where appropriate. + +2015-02-16 16:47:37 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Document locking + +2015-02-13 12:17:37 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Make get_position happen on idle + Summary: + - Add a way to force action to be executed in their own GSource dispatch, disabling chain action execution + API: + GstValidateScenario::execute-on-idle property + +2015-02-12 16:23:49 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario: Add a method to retrieve all remaining actions + Not only the next one as it was not making much sense! + API: + - gst_validate_scenario_get_next_action + + gst_validate_scenario_get_actions + +2015-02-12 16:13:09 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + validate:utils: Fix some annotations + +2015-02-12 16:10:00 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario: Allow link up of action executions for overriden types + Exposing a GstValidateActionType.overriden_type field + And properly expose gst_validate_execute_action + +2015-02-12 16:09:11 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-reporter.c: + validate:reporter: Always print reports in the Gst debug system + +2015-02-11 18:27:10 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Misc fixes + +2015-02-11 17:06:06 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Properly annotate gst_validate_register_action_type* + It does not return any reference to the type + +2015-02-10 13:50:23 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + Revert "validate:scenario: Add a way to specify action structure size" + This reverts commit b976319ef7f977b8ce910c4b8aa1a843da3b264f. + Now that the exact same structure can be used to represent different + action types, we can not rely on the structure size to stuff + informations into the action. Users should just make use of + GstMiniObject.qdata. + +2015-02-10 13:39:43 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: We do not own any ref in GstValidateExecuteAction + And gst_validate_action_set_done might very well unref the last + reference to the action + +2015-02-10 13:22:34 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Make sure that the latest action type registration is kept + Avoiding to change the behaviour! + +2015-02-07 12:51:30 +0100 Thibault Saunier + + * validate/data/change_state_intensive.scenario: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario: Handle scenario repeat property with sub actions + And port change_state_intensive.scenario to it + +2015-02-07 11:19:22 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Add the notion of sub actions + Sub action will allow user to executed action *right* after the + previous action has been completed, meaning in the end that both + action can be considered as one single action. + + Factor out a function to fill an GstValidateAction structure from a + GstStructure + + Factor out a function to set action playback time + +2015-02-06 12:20:30 +0100 Thibault Saunier + + * validate/docs/validate/envvariables.xml: + validate: Document some env variable usage + +2015-02-06 11:46:13 +0100 Thibault Saunier + + * validate/gst/plugins/fault_injection/socket_interposer.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate: Add an API to cleanly register action type from plugins + API: + gst_validate_register_action_type_dynamic + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2015-02-04 22:12:48 +0100 Thibault Saunier + + * validate/gst/plugins/fault_injection/Makefile.am: + * validate/gst/plugins/fault_injection/socket_interposer.c: + validate: Rename libfaultinjector to libgstvalidatefaultinjector + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2015-02-04 15:14:04 +0100 Thibault Saunier + + * validate/gst/plugins/fault_injection/socket_interposer.c: + * validate/gst/validate/gst-validate-scenario.c: + validate: Use plugin name as implementer_namespace when registering action type + And document it as a good practice as it will allow us to map plugins + and action types + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2015-02-04 14:54:55 +0100 Thibault Saunier + + * validate/Makefile.am: + * validate/configure.ac: + * validate/fault_injection/socket_interposer.h: + * validate/gst/Makefile.am: + * validate/gst/plugins/Makefile.am: + * validate/gst/plugins/fault_injection/Makefile.am: + * validate/gst/plugins/fault_injection/socket_interposer.c: + validate: Move the fault_injection plugin to gst/plugins/ + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2015-02-04 14:50:14 +0100 Thibault Saunier + + * validate/gst/validate/validate.c: + validate: Use an actual GstRegistry to track our plugins + Keeping everything internal for now + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2015-02-04 14:30:05 +0100 Thibault Saunier + + * validate/configure.ac: + * validate/fault_injection/Makefile.am: + * validate/fault_injection/socket_interposer.c: + * validate/fault_injection/socket_interposer.h: + * validate/gst/validate/Makefile.am: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/validate.c: + validate: Implement fault_injection as a Gs(tValidate)Plugin + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2015-02-02 18:00:14 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario: Add a way to specify action structure size + And return the register GstValidateActionType on registration + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2015-02-02 11:41:24 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Do not concider we are seek_in_paused if executing a new action + The new action might change the position on purpose and we should not + fail in that case. + Also at that point we know the test of position after the seek has + been executed + + Minor cosmetic fixes + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2015-01-20 09:59:23 +0100 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate:launcher:baseclasses: Avoid raising axception when all getting scenarios + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2015-01-13 19:07:04 +0100 Thibault Saunier + + * validate/launcher/main.py: + launcher: Use gst-integration-testsuites FDO git repo + And make sure that people that were using the old repo get the origin + repo properly updated. + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2015-02-04 15:27:37 +0100 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + validate: launcher: Allow discovering scenario from full path + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2015-02-04 15:25:50 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate: scenario: Add a method to get the following action to be executed + API: + + gst_validate_scenario_get_next_action + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2015-02-04 15:24:35 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-reporter.h: + * validate/gst/validate/gst-validate-runner.c: + validate: Add helper functions ti print actions + API: + + gst_validate_scenario_get_next_action + + gst_validate_reporter_report_simple + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2015-02-04 15:23:29 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.h: + validate: Minor documentation fixes + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2015-02-04 15:18:22 +0100 Thibault Saunier + + * validate/data/seek_with_stop.scenario: + validate: Set seek_with_stop as needing at least 2secs media files + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2014-12-13 23:23:11 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/launcher/apps/gstvalidate.py: + validate: Fix the check of action that can be *not* executed + The check was wrong and we ended up allowing seek actions to no be + executed. + API: + GST_VALIDATE_ACTION_TYPE_NO_EXECUTION_NOT_FATAL + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2014-12-13 23:16:27 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario; Advertise action types that will be executed on addition + Adding a flag to the action type + And make that code thread safe. + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2014-12-13 23:12:30 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario: Add a Flag fore ActionType that need clocks sync + And cleanly use it to set the need-clock-sync field in + the scenario properties + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2014-12-13 19:17:45 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate: Add the notion of INTERLACED actions + An interlaced action is an action that will be executed ASYNC but + without that will not block following actions during its execution. + The action should be set to done later on at any point during the + execution of the scenario. + API: + + GST_VALIDATE_EXECUTE_ACTION_INTERLACED + + GST_VALIDATE_ACTION_TYPE_INTERLACED + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2014-12-13 19:15:59 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate: Add a way to retrieve register actoin type from outside + API: + * GstValidateActionType + * gst_validate_get_action_type + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2014-12-13 16:01:49 +0100 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + validate:launcher: Implement a FakeMediaDescriptor + This allows us to more cleanly implement Simple pipeline test + generation + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2014-12-13 16:00:19 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario: Make action->scenario public API + It can be usefull for action type implementers + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2014-12-13 16:00:12 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Add a disable-plugin action type + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2014-12-12 14:41:38 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Properly advertise the wait action as ASYNC + And add some printing when executing the set-property action + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2014-12-12 14:36:16 +0100 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + validate: Enhance support for simple pipeline test generation + The GstValidatePipelineGenerator was quite limited in term + of configuration for user who just want to specify pipelines + to run with/without scenario. + Enhance the API so that we can properly configure that. + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2014-12-11 14:21:12 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/tools/gst-validate.c: + validate: Wait for switch-track to complete before executing next action + This action type can take some time, we need to make sure that the + combiner/input-selector element properly pushed a buffer marked + as DISCONT to concider the action is done. + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2014-12-11 12:08:13 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario: Avoid waiting for 50ms between actions + We should be able to execute the next action as soon as the previous + one is fully completed, make sure the code tries to do that and does + not artificially add some waiting time. + And make sure if the gst_validate_action_set_done is called from outside + our execution thread, we do not try to execute anything + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2014-12-10 20:37:58 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Add a signal to notify user when the scenario is DONE executing + https://bugzilla.gnome.org/show_bug.cgi?id=743994 + +2015-01-20 16:44:07 +0100 Ramiro Polla + + * validate/launcher/baseclasses.py: + validate: launcher: Print test name in Result + https://bugzilla.gnome.org/show_bug.cgi?id=743063 + +2015-01-16 21:29:55 +0100 Ramiro Polla + + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + validate: launcher: Add option to run tests in parallel + Patch 4/4 to implement parallel test execution. + https://bugzilla.gnome.org/show_bug.cgi?id=743063 + +2015-01-16 21:09:37 +0100 Ramiro Polla + + * validate/launcher/baseclasses.py: + validate: launcher: Print test number on result + With parallel test execution, it will be hard to track which result + relates to which test. Therefore, the test number should be printed + along with the results as well. + Patch 3/4 to implement parallel test execution. + https://bugzilla.gnome.org/show_bug.cgi?id=743063 + +2015-01-16 21:08:54 +0100 Ramiro Polla + + * validate/launcher/baseclasses.py: + validate: launcher: Use jobs list to take track of tests running + Currently the tests are still run serially. + Patch 2/4 to implement parallel test execution. + https://bugzilla.gnome.org/show_bug.cgi?id=743063 + +2015-01-16 20:35:33 +0100 Ramiro Polla + + * validate/launcher/baseclasses.py: + validate: launcher: Use test index instead of counting test numbers + Patch 1/4 to implement parallel test execution. + https://bugzilla.gnome.org/show_bug.cgi?id=743063 + +2015-01-19 10:35:03 +0100 Ramiro Polla + + * validate/launcher/RangeHTTPServer.py: + validate: launcher: Support simultaneous requests in RangeHTTPServer + https://bugzilla.gnome.org/show_bug.cgi?id=743063 + +2015-01-16 19:08:19 +0100 Ramiro Polla + + * validate/launcher/baseclasses.py: + validate: launcher: Make TestManager handle waiting for processes + Patch 4/4 to make TestManager handle waiting for processes instead of + expecting each Test to do it. + https://bugzilla.gnome.org/show_bug.cgi?id=743063 + +2015-01-16 19:03:07 +0100 Ramiro Polla + + * validate/launcher/baseclasses.py: + validate: launcher: Use a Queue to test for test completion + TestManager will use a Queue to track progress for all tests. This + commit implements a queue inside Test to simplify the transition. + Patch 3/4 to make TestManager handle waiting for processes instead of + expecting each Test to do it. + https://bugzilla.gnome.org/show_bug.cgi?id=743063 + +2015-01-16 19:00:25 +0100 Ramiro Polla + + * validate/launcher/baseclasses.py: + validate: launcher: Initialize Test start time outside of wait_process + wait_process will be moved to TestManager, so the values used to track + process update must remain inside Test. + Patch 2/4 to make TestManager handle waiting for processes instead of + expecting each Test to do it. + +2015-01-16 18:57:06 +0100 Ramiro Polla + + * validate/launcher/baseclasses.py: + validate: launcher: Split process_update() out of wait_process() + Patch 1/4 to make TestManager handle waiting for processes instead of + expecting each Test to do it. + +2015-01-16 18:50:38 +0100 Ramiro Polla + + * validate/launcher/baseclasses.py: + * validate/launcher/reporters.py: + validate: launcher: Move logfile handling out of Reporter and into Test + This makes each Test handle its own logfile, allowing the Reporter to + work on multiple tests at the same time. + Patch 5/5 to move logfile handling out of Reporter and into Test. + +2015-01-16 19:54:56 +0100 Ramiro Polla + + * validate/launcher/reporters.py: + validate: launcher: Remove redundant check + self.out is always available when _get_captured() is called. + Patch 4/5 to move logfile handling out of Reporter and into Test. + +2015-01-16 18:45:52 +0100 Ramiro Polla + + * validate/launcher/baseclasses.py: + * validate/launcher/reporters.py: + validate: launcher: Split test log file handling in Reporter + Patch 3/5 to move logfile handling out of Reporter and into Test. + +2015-01-16 18:42:19 +0100 Ramiro Polla + + * validate/launcher/baseclasses.py: + * validate/launcher/reporters.py: + validate: launcher: Separate Reporter from current Test + Instead of saving the current Test in Reporter for every test, use + function parameters to achieve the same goal. + Patch 2/5 to move logfile handling out of Reporter and into Test. + +2015-01-16 18:25:56 +0100 Ramiro Polla + + * validate/launcher/baseclasses.py: + * validate/launcher/reporters.py: + validate: launcher: Initialize reporter timer before starting all tests + Patch 1/5 to move logfile handling out of Reporter and into Test. + +2015-01-12 13:09:33 +0100 Ramiro Polla + + * validate/launcher/baseclasses.py: + validate: launcher: Don't wait for processes longer than necessary + +2015-02-03 16:48:49 +0100 Mathieu Duponchelle + + * validate/fault_injection/socket_interposer.c: + socket interposer: Be even more platform restrictive. + +2015-02-03 15:41:01 +0100 Mathieu Duponchelle + + * validate/fault_injection/socket_interposer.c: + validate: do not compile for android. + +2015-01-30 18:52:57 +0100 Mathieu Duponchelle + + * validate/Makefile.am: + * validate/configure.ac: + * validate/fault_injection/Makefile.am: + * validate/fault_injection/socket_interposer.c: + * validate/fault_injection/socket_interposer.h: + * validate/gst/validate/Makefile.am: + * validate/gst/validate/gst-validate-scenario.c: + * validate/tools/Makefile.am: + validate: Implement a fault injection library. + + And implement a corrupt-socket-recv action + + Only compile this on Linux, LD_PRELOAD won't work on Windows. + For now the registering of the action is done through + a call to socket_interposer_init, this will get better + when we refactor the action logic. + https://bugzilla.gnome.org/show_bug.cgi?id=743871 + +2015-01-23 02:04:47 +0100 Mathieu Duponchelle + + * validate/tests/check/validate/padmonitor.c: + validate: tests more issues with caps. + https://bugzilla.gnome.org/show_bug.cgi?id=743387 + +2015-01-23 01:40:59 +0100 Mathieu Duponchelle + + * validate/tests/check/validate/padmonitor.c: + * validate/tests/check/validate/test-utils.c: + validate: Add a test case for caps missing field. + + Make the fake decoder have video/x-raw caps. + https://bugzilla.gnome.org/show_bug.cgi?id=743387 + +2015-01-22 22:29:10 +0100 Mathieu Duponchelle + + * validate/tests/check/validate/padmonitor.c: + validate: prepare tests for port to tracers backend. + https://bugzilla.gnome.org/show_bug.cgi?id=743387 + +2015-01-22 22:07:37 +0100 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-bin-monitor.c: + bin-monitor: add itself as gobject data. + +2015-01-13 02:32:16 +0100 Ramiro Polla + + * validate/launcher/baseclasses.py: + validate: launcher: Fix test log header output + Write log file header before running tests, instead of overwriting the + file afterwards. + https://bugzilla.gnome.org/show_bug.cgi?id=742966 + +2015-01-15 15:32:12 +0100 Ramiro Polla + + * validate/launcher/baseclasses.py: + * validate/launcher/httpserver.py: + * validate/launcher/reporters.py: + validate: launcher: Always create log files + Create log files even when stdout redirection is enabled. + This commit partially reverts 20c28de. + https://bugzilla.gnome.org/show_bug.cgi?id=742973 + +2015-01-15 15:26:14 +0100 Ramiro Polla + + * validate/launcher/baseclasses.py: + * validate/launcher/httpserver.py: + * validate/launcher/main.py: + * validate/launcher/reporters.py: + validate: launcher: Introduce new parameter for log file redirecting + Allow log file redirection through the new --redirect-logs parameter. + Keep the old --logs-dir stdout/stderr parameter, but reset to the + default logs directory in that case, and set redirect_logs internally. + This also prevents the creation of an stdout/stderr directory for + writing xunit.xml. + https://bugzilla.gnome.org/show_bug.cgi?id=742973 + +2015-01-09 14:04:16 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: plug caps leak on iterator resync + +2015-01-09 12:36:31 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: use the same filter caps when querying downstream caps + To avoid comparing the real result that has been filtered against + a much larger caps that contains all possibilities. + +2014-12-09 10:09:15 +0100 Thibault Saunier + + * validate/tests/check/validate/test-utils.c: + validate: tests: disable g_log handler + It messes up our own failures counter + And pass test-utils into gst-indent + +2014-12-08 18:53:55 -0300 Thiago Santos + + * validate/tests/check/validate/padmonitor.c: + tests: padmonitor: disable glog handling + It messes up our own failures counter + +2014-12-08 17:27:52 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: get correct caps to check for proxied fields in caps queries + Elements should proxy the peer element's caps fields and not what they + have currently set on their pads when replying to a caps query + +2014-12-08 17:17:08 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: Only add pending caps fields for source pads + As caps events are downstream, caps set travels from sinks to + sources. Adding pending setcaps values to sink pads makes no sense + as when a new caps is set on the sink it would compare with values + currently set on the source pad, causing a critical failure when + renegotiation happens. + +2014-12-08 18:23:10 +0100 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate: launcher: Take the timeout as ref timeout to compute hard_timeout + when it is provided. + +2014-12-08 15:27:54 +0100 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + validate: launcher: Set a hard timeout on GstValidate tests if we know the duration + +2014-12-08 14:37:15 +0100 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate: launcher: Force kill subprocess when done with them + Making sure that we do not end up having spurious subprocess around + +2014-12-08 08:42:51 -0300 Thiago Santos + + * validate/launcher/baseclasses.py: + launcher: baseclass: add missing parameter + Fixes "NameError: global name 'options' is not defined" + +2014-12-08 10:09:57 +0100 Thibault Saunier + + * validate/launcher/main.py: + validate: launcher: Properly handle non default main dir + for the case of the new testsuite files + +2014-12-07 12:30:25 +0100 Thibault Saunier + + * validate/launcher/apps/__init__.py: + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/main.py: + validate: launcher: Make the gstvalidate application a python module + +2014-12-06 10:53:37 +0100 Thibault Saunier + + * validate/configure.ac: + validate: Remove remaining reference to launcher/apps/validate + It has been removed now. + +2014-12-03 11:28:28 +0100 Thibault Saunier + + * validate/tools/gst-validate.c: + validate: Avoid assert removing an already removed signal handler + And, make sure that we set the return value != 0 when we receive + SIGINT + +2014-12-02 17:32:18 +0100 Thibault Saunier + + * validate/launcher/main.py: + validate: Handle setting the HTTP server local path from testsuites + +2014-12-02 15:41:17 +0100 Thibault Saunier + + * validate/launcher/main.py: + * validate/launcher/utils.py: + validate: Rename gst-qa-assets to gst-integration-testsuites + +2014-12-02 15:39:09 +0100 Thibault Saunier + + * validate/tools/gst-validate.c: + validate: print execution of set_subtitles actions + +2014-12-02 10:02:09 +0100 Thibault Saunier + + * validate/launcher/main.py: + validate: Factor out an LauncherConfig class type to handle configurations + Allowing us to more simply define default value and expose an API on + top of it + +2014-12-02 10:00:42 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/launcher/apps/gstvalidate.py: + validate: Remove file specific blacklisted tests + +2014-11-29 13:43:06 +0100 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate: Let the user know when new tests are added, or tests are REMOVED + +2014-11-29 00:03:04 +0100 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + validate: Add a cleaner API to setup tests in testsuite files + With the testsuite format you will get a setup_tests(tests_manager, + options) function called for each TestManager. + The function will have the exact same role as with old config + file but with a clean API and not magic global variables. + This implies that we need default blacklist to be directly set + on the TestManager and not on options.blacklisted_test + +2014-11-28 22:58:09 +0100 Thibault Saunier + + * validate/launcher/main.py: + validate: Add a way to sync all assets, including big ones + +2014-11-28 22:42:47 +0100 Thibault Saunier + + * validate/launcher/apps/Makefile.am: + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/apps/validate/Makefile.am: + * validate/launcher/apps/validate/validate_testsuite.py: + * validate/launcher/main.py: + * validate/launcher/utils.py: + validate: Remove the default testsuite implementation + The default testsuite implementation should belong to the default + asset repo where we have the corresponding knowledge. + We should style manage a sensible list of known blacklisted tests, + encoding profiles, and generators in GstValidate itself and allow testsuite + actual implementations to easily use them though the register_default_* + methods. + This allow us to be able to remove the ugly execfile() call. + +2014-11-27 12:11:43 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-reporter.c: + validate: Make sure to at least listen to GStreamer and GLib g_logs + If somewhere else someone is overriding the g_log default handler, + we would not get notified of anything. + +2014-11-27 13:48:17 +0100 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate: Disable coloration of GST_DEBUG logs when we have no-color + Do that only when those logs are not saved to a file + +2014-11-26 17:50:11 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/media-descriptor-parser.h: + * validate/gst/validate/media-descriptor.h: + validate: Factor out a function to print action types parametters + + Remove playback-type from the list and just print it + +2014-11-19 17:16:02 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-element-monitor.c: + * validate/gst/validate/gst-validate-element-monitor.h: + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: do not enforce caps querying rules for converters + Some encoders/decoders can also be converters, do not enforce + caps proxying rules for them + +2014-11-28 11:14:12 +0530 Vineeth T M + + * validate/launcher/main.py: + validate: fix typo in documentation + There are some typing mistakes in gst-validate-launcher --help + Hence fixing the same. + https://bugzilla.gnome.org/show_bug.cgi?id=740833 + +2014-11-25 15:35:09 +0100 Thibault Saunier + + * validate/launcher/apps/validate/validate_testsuite.py: + * validate/launcher/main.py: + validate: Handle unlimited tests duration + Running full length scenario when the user asks + +2014-11-25 15:32:31 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-element-monitor.c: + validate: Already having a monitor is no error + +2014-11-25 15:30:42 +0100 Thibault Saunier + + * validate/pre-commit-python.hook: + validate: pre commit hook: Do not try to run pep8 on non python files! + +2014-11-25 15:29:29 +0100 Thibault Saunier + + * validate/data/Makefile.am: + * validate/data/scrub_backward_seeking_full.scenario: + * validate/data/scrub_forward_seeking_full.scenario: + validate: Add scub_*_seeking_full scenarios + Which basically do the same thing as scrub_*_seeking but during + throughout the whole duration of the media + +2014-11-21 19:35:16 +0100 Thibault Saunier + + * validate/docs/validate/gst-validate-docs.sgml: + * validate/docs/validate/gst-validate-sections.txt: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-reporter.h: + * validate/gst/validate/gst-validate-runner.h: + validate: Enhance documentation + +2014-11-19 17:58:23 +0100 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/main.py: + validate: Add an option to update all .media_info files + +2014-11-16 23:05:45 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Execute actions without playback time without a valid position + If the user did not specify any playback time we should be able to + execute actions even if the pipeline can't answer the position query + + Make simpler to read the conditions of an action execution + +2014-11-09 19:08:52 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario: Properly handle ASYNC action execution in the API + The ->execute function now return a GstValidateExecuteActionReturn + which can be set as ASYNC in order to tell the scenario that the action + will be executed asynchronously, when the action is done, the caller is + responsible for calling gst_validate_action_set_done(); so that the + scenario keeps going on. + In this commit we make sure that the old API keeps working as + GST_VALIDATE_EXECUTE_ACTION_ERROR == FALSE and + GST_VALIDATE_EXECUTE_ACTION_OK == TRUE + Morevover GstValidateExecuteActionReturn is just a define + API: + + gst_validate_action_set_done + + GstValidateExecuteActionReturn + https://bugzilla.gnome.org/show_bug.cgi?id=739854 + +2014-11-07 23:19:59 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate: Add a GstValidateActionTypeFlag flag + Allowing us to define action types more in detail. + Keep backward compatibility, at least with the C API + https://bugzilla.gnome.org/show_bug.cgi?id=739854 + +2014-11-21 14:01:48 +0100 Thibault Saunier + + * validate/gst/validate/Makefile.am: + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-bin-monitor.h: + * validate/gst/validate/gst-validate-monitor-factory.c: + * validate/gst/validate/gst-validate-pipeline-monitor.c: + * validate/gst/validate/gst-validate-pipeline-monitor.h: + validate: Add a GstValidatePipelineMonitor subclass + We had quite a bit of code dedicated to handled GstPipeline monitoring + inside GstValidateBinMonitor, cleanly split that code into a new object + type + https://bugzilla.gnome.org/show_bug.cgi?id=740704 + +2014-11-20 11:55:45 +0100 Thibault Saunier + + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/baseclasses.py: + * validate/launcher/main.py: + * validate/launcher/utils.py: + validate:launcher: Force clock sync for some protocols + In HLS for example, not having clock sync might lead to races and failures + do not test that for now + +2014-11-20 11:53:34 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + validate: Don't fail getting master report from a ghostpad without target + +2014-11-19 20:05:57 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-bin-monitor.c: + validate: Print current position even if we do not know the rate + That could cause gst-validate-launcher to wrongly concider tests + as timeout + +2014-11-17 11:39:12 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Force clock sink for scenarios with a pause action + +2014-11-15 18:08:42 +0100 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate: Handle wrong paths when listing avalaible apps + User can make mistake or we can have an empty path. + +2014-11-11 20:56:04 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/tests/check/validate/padmonitor.c: + validate: Do not check if first buffer running time is 0 + It can perfectly not be 0, so it makes no sense to check that. + https://bugzilla.gnome.org/show_bug.cgi?id=739965 + +2014-11-06 23:43:47 +0100 Thibault Saunier + + * validate/launcher/apps/validate/validate_testsuite.py: + validate: Add audiomixer test to the default testsuite + +2014-11-01 09:24:15 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: Give better details about segment mismatch issues + +2014-10-30 14:10:33 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + validate: Be more precise in issue type for wrong seqnum + Depending on the type of event where the bug occurs, + it is not the same issue type. That allows us to have + much precise reports, and better explain the user + where the issue stands. + +2014-10-16 17:32:56 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-monitor-factory.c: + * validate/gst/validate/gst-validate-scenario.c: + validate: Fix a few annotation issues + +2014-10-15 17:03:48 +0200 Thibault Saunier + + * validate/docs/validate/envvariables.xml: + * validate/gst/validate/gst-validate-scenario.c: + validate: Add the notion of WAIT_MULTIPLIER for the wait action + Allowing the user to decide to wait more, or less, or even not wait + for the wait action to execute when running scenarios. + +2014-09-28 22:37:01 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Report an EXECUTION_ERROR on action execution failure + If the action type handles a better error report type, it should just + return TRUE, and report its issue itself. + +2014-09-19 09:13:13 +0200 Thibault Saunier + + * validate/launcher/baseclasses.py: + validate: Do not exit when we can not discover a result file + Loggable.error actually exit the process, it is not what we want! + + Avoid a backtrace + +2014-11-03 11:50:54 +0100 Edward Hervey + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Allow set-property action to work much earlier + By default an action has no playback-time, this makes it actionable + immediatly. + When no playback-time is set on a set-property action, it will + be activated the moment the element is added in the pipeline. + +2014-10-31 16:01:52 +0100 Edward Hervey + + * validate/gst/validate/gst-validate-bin-monitor.c: + validate-bin-monitor: Initialize local variable + Avoids segfaults when freeing them if they didn't get filled in + +2014-10-26 14:47:12 +0100 Mathieu Duponchelle + + * validate/autogen.sh: + * validate/multi-pre-commit.hook: + * validate/pre-commit-python.hook: + validate: update pre-commit hook. + + Allows to run multiple pre-commit hooks. + + Always relink the hooks on autogen. + + Run pep8 on commited python files. + https://bugzilla.gnome.org/show_bug.cgi?id=739208 + +2014-10-25 14:59:49 +0200 Mathieu Duponchelle + + * validate/launcher/apps/gstvalidate.py: + apps: gstvalidate.py: fix various pyflakes / uncaught pep8 issues. + https://bugzilla.gnome.org/show_bug.cgi?id=739208 + +2014-10-25 14:50:54 +0200 Mathieu Duponchelle + + * validate/launcher/utils.py: + validate-launcher: utils: fix various pyflakes / uncaught pep8 issues. + https://bugzilla.gnome.org/show_bug.cgi?id=739208 + +2014-10-25 14:49:26 +0200 Mathieu Duponchelle + + * validate/launcher/main.py: + validate-launcher: main: fix various pyflakes / uncaught pep8 issues. + https://bugzilla.gnome.org/show_bug.cgi?id=739208 + +2014-10-25 14:46:26 +0200 Mathieu Duponchelle + + * validate/launcher/loggable.py: + validate-launcher: loggable: fix various pyflakes / uncaught pep8 issues. + https://bugzilla.gnome.org/show_bug.cgi?id=739208 + +2014-10-24 14:38:00 +0200 Mathieu Duponchelle + + * validate/launcher/baseclasses.py: + validate-launcher: baseclasses: fix various pyflakes / uncaught pep8 issues. + https://bugzilla.gnome.org/show_bug.cgi?id=739208 + +2014-10-24 14:23:52 +0200 Mathieu Duponchelle + + * validate/launcher/RangeHTTPServer.py: + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/apps/validate/validate_testsuite.py: + * validate/launcher/baseclasses.py: + * validate/launcher/httpserver.py: + * validate/launcher/loggable.py: + * validate/launcher/main.py: + * validate/launcher/reporters.py: + * validate/launcher/utils.py: + validate-launcher: pep8ify sources. + https://bugzilla.gnome.org/show_bug.cgi?id=739208 + +2014-10-23 21:43:45 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-utils.c: + validate-utils: downgrade ERROR to DEBUG. + This function is called in places where it is legit for it + to return NULL. + +2014-10-23 21:36:03 +0200 Mathieu Duponchelle + + * validate/launcher/baseclasses.py: + launcher: add a way to specify an application directory. + https://bugzilla.gnome.org/show_bug.cgi?id=739091 + +2014-10-23 21:34:27 +0200 Mathieu Duponchelle + + * validate/launcher/apps/Makefile.am: + * validate/launcher/apps/geslaunch.py: + launcher: Don't implement product-specific TestManagers. + This manager will be moved in GES. + https://bugzilla.gnome.org/show_bug.cgi?id=739091 + +2014-10-23 15:21:14 +0200 Mathieu Duponchelle + + * validate/Makefile.am: + * validate/configure.ac: + * validate/launcher/Makefile.am: + * validate/launcher/RangeHTTPServer.py: + * validate/launcher/__init__.py: + * validate/launcher/apps/Makefile.am: + * validate/launcher/apps/geslaunch.py: + * validate/launcher/apps/gstvalidate.py: + * validate/launcher/apps/validate/Makefile.am: + * validate/launcher/apps/validate/validate_testsuite.py: + * validate/launcher/baseclasses.py: + * validate/launcher/httpserver.py: + * validate/launcher/loggable.py: + * validate/launcher/main.py: + * validate/launcher/reporters.py: + * validate/launcher/utils.py: + * validate/tools/Makefile.am: + * validate/tools/gst-validate-launcher.in: + validate-launcher: restructure filesystem + https://bugzilla.gnome.org/show_bug.cgi?id=739091 + +2014-10-24 18:41:30 +0530 Ramprakash Jelari + + * validate/gst/validate/gst-validate-reporter.c: + validate: Fix compiler warning about implicit enum type conversion + gst-validate-reporter.c:119:39: error: implicit conversion from enumeration type + 'GstValidateReportingDetails' to different enumeration type + 'GstValidateInterceptionReturn' [-Werror,-Wenum-conversion] + GstValidateInterceptionReturn ret = GST_VALIDATE_SHOW_UNKNOWN; + ~~~ ^~~~~~~~~~~~~~~~~~~~~~~~~ + gst-validate-reporter.c:124:11: error: implicit conversion from enumeration type + 'GstValidateReportingDetails' to different enumeration type + 'GstValidateInterceptionReturn' [-Werror,-Wenum-conversion] + ret = iface->get_reporting_level (reporter); + ~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + gst-validate-reporter.c:127:10: error: implicit conversion from enumeration type + 'GstValidateInterceptionReturn' to different enumeration type + 'GstValidateReportingDetails' [-Werror,-Wenum-conversion] + return ret; + ~~~~~~ ^~~ + +2014-10-22 14:16:45 +0200 Mathieu Duponchelle + + * validate/gst/validate/Makefile.am: + build: We install all headers system wide for now. + Will be fixed when the API is deemed stable enough + +2014-10-21 23:31:37 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-enums.h: + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-monitor.h: + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-reporter.h: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-runner.h: + * validate/tests/check/validate/overrides.c: + * validate/tests/check/validate/padmonitor.c: + * validate/tests/check/validate/reporting.c: + validate: rename GstValidateReportingLevel. + Removes the confusion with GstValidateReportLevel. + Modeled on GstDebugGraphDetails. + +2014-10-18 18:55:59 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/tests/check/validate/padmonitor.c: + validate: Verify that elements always send a segment before pushing EOS + EOS is some kind of data flow and thus a segment event should always be + pushed before the EOS is sent + +2014-10-18 18:53:03 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/tests/check/validate/padmonitor.c: + * validate/tests/check/validate/reporting.c: + validate: Properly check that the seqnum of the EOS is always properly set + In the pipeline, an EOS should always have the same seqnum of the + previous SEGMENT event that was received. If the segment is the result + of a seek, it should always be the same as the seek seqnum too. + + (Mathieu Duponchelle): fix reporting and concatenation tests. + +2014-10-03 18:51:17 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/tests/check/Makefile.am: + * validate/tests/check/validate/overrides.c: + validate: Add support for text based override files + Allowing user to easily determine the severity of issue + types in a config file + https://bugzilla.gnome.org/show_bug.cgi?id=737852 + +2014-10-03 18:53:42 +0200 Thibault Saunier + + * validate/gst/validate/media-descriptor-parser.c: + * validate/gst/validate/media-descriptor-parser.h: + validate: Remove unused method + gst_media_descriptor_add_frame is not used anywhere + https://bugzilla.gnome.org/show_bug.cgi?id=737852 + +2014-10-03 18:42:04 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/gst-validate-utils.h: + validate: Move GstStructure file parsing into utils + So it can be reused, at least in GstValidate. + +2014-10-02 15:34:28 +0200 Thibault Saunier + + * validate/gst/overrides/gst-validate-default-overrides.c: + * validate/gst/validate/gst-validate-media-info.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-reporter.h: + * validate/gst/validate/media-descriptor-writer.c: + * validate/tests/check/validate/padmonitor.c: + validate: report: Simplify the issue ID registering using GQuarks + + Remove unused issue types + https://bugzilla.gnome.org/show_bug.cgi?id=737790 + +2014-09-15 17:27:54 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.h: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/tests/check/validate/padmonitor.c: + * validate/tests/check/validate/test-utils.c: + * validate/tests/check/validate/test-utils.h: + * validate/tools/launcher/apps/gstvalidate.py: + validate: Check all buffers when we have the info from MediaDescriptor + We now check that each buffer is the expected one for each buffer that + come into the decoder. + + Fix some minor leaks in test-utils + https://bugzilla.gnome.org/show_bug.cgi?id=736138 + +2014-10-02 11:27:30 +0200 Thibault Saunier + + * validate/gst/validate/media-descriptor-parser.c: + * validate/gst/validate/media-descriptor-parser.h: + validate:media-descriptor-parser: Add a way to create from a string + So it is simple to make use of it from the testsuite + https://bugzilla.gnome.org/show_bug.cgi?id=736138 + +2014-10-01 16:24:58 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + report: g_critical are CRITICAL issues! + https://bugzilla.gnome.org/show_bug.cgi?id=736138 + +2014-09-17 17:32:52 +0200 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + validate: launcher: Fix printing of errors in final report + https://bugzilla.gnome.org/show_bug.cgi?id=736138 + +2014-09-15 19:14:27 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gstvalidate.py: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/main.py: + validate: Add the possibility to generate media infos with frame descs + + Fix a little issue when the generation fails. + https://bugzilla.gnome.org/show_bug.cgi?id=736138 + +2014-09-15 17:26:23 +0200 Thibault Saunier + + * validate/gst/validate/media-descriptor-parser.c: + * validate/gst/validate/media-descriptor-writer.c: + * validate/gst/validate/media-descriptor.h: + validate: MediaDescriptors: Add md5sum to buffer informations + In the media descriptor files, we now have the md5sum of the actual + content of encoded buffers so that we can check that the buffer content is + perfectly what is was supposed to be. + + Fix the check of whether a frame is a keyframe in the string + comparison (g_ascii_strcasecmp return 0 if string matches) + https://bugzilla.gnome.org/show_bug.cgi?id=736138 + +2014-09-15 17:25:14 +0200 Thibault Saunier + + * validate/gst/validate/media-descriptor-parser.h: + validate: Move some method between GstMediaDescriptorParser and GstMediaDescriptor + So that method land where they actually belong. + https://bugzilla.gnome.org/show_bug.cgi?id=736138 + +2014-09-15 17:22:52 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-element-monitor.c: + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-monitor.h: + * validate/tools/gst-validate.c: + validate: Add a way to pass a MediaDescriptor around monitors + And add an option in gst-validate so that the user can define what + media descriptor file to use. + https://bugzilla.gnome.org/show_bug.cgi?id=736138 + +2014-09-12 12:12:14 +0200 Thibault Saunier + + * validate/gst/validate/media-descriptor-writer.c: + validate:media-descriptor: Handle stream with no tags + It was segfaulting before. + +2014-09-17 16:51:20 +0200 Thibault Saunier + + * .gitignore: + * validate/.gitignore: + * validate/docs/.gitignore: + * validate/docs/validate/.gitignore: + validate: Add more files to gitignore + +2014-09-15 17:25:14 +0200 Thibault Saunier + + * validate/gst/validate/media-descriptor-parser.c: + * validate/gst/validate/media-descriptor-parser.h: + * validate/gst/validate/media-descriptor.c: + * validate/gst/validate/media-descriptor.h: + validate: Move some method between GstMediaDescriptorParser and GstMediaDescriptor + So that method land where they actually belong. + +2014-10-12 16:25:25 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-runner.c: + * validate/tests/check/validate/reporting.c: + validate-report / reporter: rework the way we repeat issues. + + runner: update reports count algorithm. + +2014-10-12 16:13:51 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: mark the peer pad as EOS too. + When a sink pad gets EOS, its src pad monitor should also + be marked as EOS (helpful with issue concatenation). + +2014-10-10 10:22:31 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-runner.c: + * validate/tests/check/validate/reporting.c: + validate-pad-monitor / runner: Check per-object reporting levels. + +2014-10-12 14:36:13 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + validate-report: Set conditions in which a report can't be master. + +2014-10-12 14:34:34 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + validate-report: Add a reporting level field and setter. + +2014-10-21 19:43:45 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-runner.c: + * validate/tests/check/validate/padmonitor.c: + * validate/tests/check/validate/reporting.c: + validate-runner: implement synthetic report. + + Fix criticals logic in validate_runner_printf + + Update padmonitor tests + + Split validate_report_printf function. + +2014-10-10 06:01:03 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-runner.c: + * validate/tests/check/validate/reporting.c: + validate-runner: Implement REPORT_NONE for global reporting. + Yeah that was tough. Helpful already though, for example: + GST_VALIDATE_REPORT_LEVEL=none,x:all gst-validate src name=x ! sink + will only report issues reported by the source. + + Add test. + +2014-10-10 05:08:28 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-reporter.h: + * validate/tests/check/validate/reporting.c: + tests: Check monitors correctly determine their reporting level. + + [API] gst_validate_reporter_get_reporting_level + +2014-10-10 03:55:37 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-runner.c: + validate-runner / monitor: Let the user single out pads. + That's some pretty specific code but it should be helpful. + The following syntax can be used : element-name::pad-name. + + Free return of gst_object_get_name. + +2014-10-10 02:52:26 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-runner.c: + validate-runner / reporter: Sanitize reports refcounting. + The previous code worked but was confusing, the runner didn't actually + take the ref it was releasing later. + + Fix indentation. + +2014-10-10 02:49:54 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-element-monitor.c: + * validate/tests/check/validate/padmonitor.c: + * validate/tests/check/validate/test-utils.c: + * validate/tests/check/validate/test-utils.h: + tests: Test reports refcounts. + + Set the element monitor on the element as qdata. + +2014-10-10 01:17:43 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-monitor.h: + validate-monitor: Determine the reporting level at setup. + +2014-10-09 19:41:48 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-runner.h: + * validate/tests/check/validate/reporting.c: + validate-runner: Add code to parse GST_VALIDATE_REPORT_LEVEL. + + Extend the tests. + + [API] gst_validate_runner_get_default_reporting_level + + [API] gst_validate_runner_get_reporting_level_for_name + +2014-10-08 05:08:21 +0200 Mathieu Duponchelle + + * validate/gst/validate/Makefile.am: + * validate/gst/validate/gst-validate-enums.h: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-runner.h: + * validate/tests/check/Makefile.am: + * validate/tests/check/validate/reporting.c: + validate-runner: report-level initial work. + + Defines reporting levels and document them. + + Add API to get the default level. + + fix indentation. + + fix some typos. + + Add the beginning of a reporting test. + +2014-10-02 02:50:29 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/tests/check/validate/padmonitor.c: + validate-pad-monitor: concatenate issues. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=735665 + The process is to check for a similar report in intercept_report on + the pads of the upstream element, set that report as the master report + of the intercepted report, and return REPORTER_KEEP instead + of REPORTER_REPORT. + +2014-10-02 02:34:26 +0200 Mathieu Duponchelle + + * validate/tests/check/validate/test-utils.c: + * validate/tests/check/validate/test-utils.h: + test-utils: add a create_and_monitor element function. + +2014-10-01 18:28:33 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-reporter.h: + validate-reporter: Add some methods + + gst_validate_reporter_get_reports + + gst_validate_reporter_get_reports_count + +2014-10-01 15:53:24 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + validate-report: Add the notion of master / shadow reports. + A master report is a report that has been detected by a monitor + to stem from the same issue. It thus contains a list of + "shadow reports" which it will browse when printing itself. + +2014-10-01 15:50:11 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-report.c: + validate-report: Make the ref / unref functions safer. + +2014-10-21 13:07:02 +0200 Mathieu Duponchelle + + * validate/tests/check/validate/padmonitor.c: + tests/padmonitor: Correcly strdup the result of get_metadata. + The const pointer was becoming invalid after the first call to add_metadata, + and we ended up setting corrupted data on the second call. + +2014-10-01 15:11:21 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-reporter.h: + * validate/gst/validate/gst-validate-scenario.c: + validate-reporter: Add return value to intercept_report. + It will allow to drop, keep or report reports. + +2014-09-30 16:08:46 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-pad-monitor.c: + validate-pad-monitor: Reimplement reporter interface. + + Do nothing there for now, except chain up. + +2014-09-30 14:52:35 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-reporter.h: + validate-reporter: add gst_validate_reporter_get_report. + + Add locking. + +2014-10-20 13:38:20 +0200 Thibault Saunier + + * validate/configure.ac: + Back to development + +2014-10-20 12:04:25 +0200 Thibault Saunier + + * validate/ChangeLog: + * validate/NEWS: + * validate/configure.ac: + Release 1.4.0 + +2014-10-13 16:28:54 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-bin-monitor.c: + validate: Print position if it could properly be queried + Otherwize we will print meaningless garbage. + +2014-10-13 10:32:07 +0200 Thibault Saunier + + * validate/docs/launcher/conf.py: + * validate/tools/launcher/baseclasses.py: + validate:launcher: Minor enhancement in the documentation + +2014-10-12 20:19:42 +0200 Thibault Saunier + + * validate/data/adaptive_video_framerate.scenario: + * validate/data/adaptive_video_framerate_size.scenario: + * validate/data/adaptive_video_size.scenario: + * validate/data/alternate_fast_backward_forward.scenario: + * validate/data/camerabin_signal.scenario: + * validate/data/disable_subtitle_track_while_paused.scenario: + * validate/data/fast_backward.scenario: + * validate/data/fast_forward.scenario: + * validate/data/force_key_unit.scenario: + * validate/data/pause_resume.scenario: + * validate/data/play_15s.scenario: + * validate/data/reverse_playback.scenario: + * validate/data/scrub_backward_seeking.scenario: + * validate/data/scrub_forward_seeking.scenario: + * validate/data/seek_backward.scenario: + * validate/data/seek_forward.scenario: + * validate/data/seek_forward_backward.scenario: + * validate/data/seek_with_stop.scenario: + * validate/data/simple_seeks.scenario: + * validate/data/switch_audio_track.scenario: + * validate/data/switch_audio_track_while_paused.scenario: + * validate/data/switch_subtitle_track.scenario: + * validate/data/switch_subtitle_track_while_paused.scenario: + * validate/data/update_start.scenario: + * validate/data/update_stop.scenario: + * validate/docs/validate/scenarios.xml: + * validate/gst/validate/gst-validate-scenario.c: + validate: Rename action type playback_time to playback-time + Keeping backward compatiblity with the old naming + +2014-10-12 20:07:58 +0200 Thibault Saunier + + * validate/docs/validate/gst-validate-sections.txt: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: Rename gst_validate_add_action_type to gst_validate_register_action_type + The _register naming corresponds much better to what the method does + and makes it more similar to how we refer to this kind of action in + GStreamer. + It is a last minute API change, but that API should not change anymore + after 1.4 is released. + +2014-10-12 20:00:03 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Fix the addition of playback_time in the parameter types + +2014-10-12 19:46:39 +0200 Thibault Saunier + + * validate/docs/validate/scenarios.xml: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: Rename --list-action-types to --inspect-action-type + Making clearer the meaning of the parameter and closer to the + usual naming in the GStreamer land. + +2014-10-12 19:16:08 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Add the 'flags' for the seek action type + This was always a mandatory field but was not documented + +2014-09-29 10:22:55 +0530 Anuj Jaiswal + + * validate/gst/validate/gst-validate-runner.c: + validate: mishandled pointer criticals + Free glist of criticals + Signed-off-by: Anuj Jaiswal + https://bugzilla.gnome.org/show_bug.cgi?id=736313 + +2014-10-01 10:54:47 +0200 Thibault Saunier + + * validate/docs/validate/command-line-tools.xml: + validate:docs: Add documentation about the default testsuite + +2014-09-30 10:30:24 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-runner.h: + * validate/tests/check/validate/padmonitor.c: + validate-runner: switch to using a GList for the reports. + + Return a copy of that list in get_reports. + + update tests. + +2014-09-30 09:24:48 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-runner.h: + * validate/tests/check/validate/padmonitor.c: + validate-runner: Hide implementation. + +2014-09-30 09:11:58 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-runner.h: + gst-validate-runner: Add locking for the reports list. + +2014-09-29 15:37:40 +0200 Thibault Saunier + + * validate/ChangeLog: + * validate/NEWS: + * validate/configure.ac: + * validate/docs/release.txt: + Release 1.3.90 + +2014-09-12 10:47:18 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gstvalidate.py: + * validate/tools/launcher/baseclasses.py: + validate:launcher: Factorize code to get a MediaDescriptor name for classname + Instead of copy/pasting that code badly + +2014-09-12 10:22:15 +0200 Edward Hervey + + * validate/docs/validate/gst-validate.types: + validate/docs: Add location of Scenario/Action defines + +2014-09-10 16:45:41 +0530 Anuj Jaiswal + + * validate/gst/validate/gst-validate-media-info.c: + validate: (performance issue)refactor to remove duplicate assignment + Signed-off-by: Anuj Jaiswal + https://bugzilla.gnome.org/show_bug.cgi?id=736412 + +2014-09-11 10:54:43 +0200 Thibault Saunier + + * validate/docs/Makefile.am: + validate: docs: Always dist the launcher directoty + Fixing make distcheck + +2014-09-11 09:42:02 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-runner.c: + validate: Get the Runner reports in order of arrival + Making sure they are printed in the right order + +2014-09-10 09:47:22 +0200 Thibault Saunier + + validate: Start a testsuite + Currently implemented tests are: + * Settup and cleanup on monitor is done properly + * Some tests in the PadMonitor are done properly, namely: + - Buffer before segment + - Buffer outside segment + - First buffer running time is always 0 + - The Demuxer flow aggregation is properly checked + https://bugzilla.gnome.org/show_bug.cgi?id=736379 + +2014-09-12 09:49:35 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-internal.h: + validate/private: Avoid double typdef + Instead just include required (public and local) header + gst-validate-scenario.h:43:44: error: redefinition of typedef 'GstValidateActionParameter' is a C11 feature [-Werror,-Wtypedef-redefinition] + +2012-09-26 02:28:00 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + timeline: disable tooltip while scrolling + +2012-09-26 02:00:10 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI/columns.py: + Hide some columns by default + +2012-09-26 01:56:05 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI/columns.py: + columns: optimize cell data functions a little + +2012-09-26 01:41:22 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI/window.py: + Fix crash when copying row to clipboard + +2012-09-24 22:58:58 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + timeline: fix incorrect position after scrolling using the timeline + Apparently events are dropped internally, so the last position after you stop + dragging can be off. + +2012-09-23 16:43:25 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI/models.py: + models: store line offsets in arrays + +2012-09-23 17:22:53 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI/columns.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + Use pango markup instead of attributes + Attributes don't work from introspection, so this blocks porting to gtk3. + In MessageColumn, admit that multiple highlighters don't actually work. + +2012-09-23 17:22:12 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + findbar: highlight multiple matches in a message + +2012-09-24 02:15:09 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + timeline: only redraw updated parts of the graph + Improves rendering performance a lot. + +2012-09-24 02:23:22 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + timeline: fix position rectangle missing on first click + Regression caused by previous commit. + +2012-09-22 01:27:37 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + timeline: only redraw required areas when updating position rectangle + +2012-09-22 01:25:22 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI/models.py: + Fix crash when range filtering + Regression from 25cfe9 (timeline: make log level calculation a lot faster). + +2012-09-22 00:33:41 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + timeline: clean up widget drawing + +2012-09-21 22:52:25 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI/models.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + timeline: make log level calculation a lot faster + This is the step where the timeline graph gets colored with the individual log + level colors. It's roughly 4.5 times faster now. Probably can be made even + better, the code also needs a cleanup. + +2012-09-21 22:15:07 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + Data: improve stripped log file loading performance + A ~9% improvement for files without colors. This now slightly outperforms the + code before color support was added. + +2012-09-21 21:38:58 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + Data: also yield while loading unparseable files + Otherwise, the UI would be blocked while loading something big that is not a + log file at all. + +2012-09-21 19:13:07 +0200 René Stadler + + * debug-viewer/gst-debug-strip-color.py: + Remove color stripping script + +2012-09-21 19:11:40 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + Data: add support for colored log files + Adds a ~5% penalty for loading stripped files. + +2012-09-21 00:40:07 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + timeline: remove broken actions from context menu + Hide lines before/after doesn't work as expected in this case. + +2012-09-20 23:51:05 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + Data: fix parsing of lines missing filename or function name + E.g. ffmpeg. + +2012-09-20 20:20:58 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + timeline: grab when scrolling in TimelineWidget + Also use gdk_event_request_motions. + +2012-09-20 20:11:48 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + timeline: move mouse handling into TimelineWidget + +2012-09-20 19:58:06 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + timeline: replace self.connect calls with vmethod overrides + +2012-09-17 18:39:53 +0200 Andrzej Bieniek + + * debug-viewer/setup.py: + setup: fix build + +2012-08-27 13:52:56 -0700 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + timeline: don't select row when changing position in the timeline + Behaves just like the scrollbar now. + +2012-08-27 13:46:14 -0700 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + timeline: stop scanning the file while filtering + +2012-08-27 13:45:57 -0700 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + timeline: small cleanup + +2012-08-24 02:09:04 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/data/menus.ui: + Modernize menus a little + A bit in preparation to gtk3 app menus. + +2012-08-24 01:50:44 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI/models.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + Simplify and optimize filtered model implementation + RangeFilteredLogModel is gone. The functionality is trivially implemented in + FilteredLogModel now. Changing the range is now O(log n) at worst (was O(n) at + best, for rewriting the arrays). Stacking filtered models is not supported + anymore, which simplifies the code. + +2012-08-24 01:42:00 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI/models.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + Make RangeFilteredLogModel internal to GUI.models + +2012-08-24 01:40:24 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI/models.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + Always use a filtered log model in the log view + Preparing to phase out RangeFilteredLogModel. + +2012-08-24 01:37:27 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI/models.py: + models: cleanup dead code + +2012-03-31 01:16:25 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI/window.py: + window: prevent default handler for delete-event from running + +2012-08-24 01:26:32 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI/window.py: + window: set to insensitive during load/filter operations + +2012-08-24 01:20:05 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI/window.py: + window: show error for unparseable files + +2012-08-24 00:24:55 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/data/progress-dialog.ui: + window: replace progress and error dialogs with InfoBars + +2012-08-24 00:10:05 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI/window.py: + window: set wmclass, to have a nicer app name when running uninstalled + +2011-11-16 20:37:21 +0100 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + Data: fix up out-of-order log lines + This is important because we rely on monotonically increasing timestamps for + binary searches in various places. + Overhead for an already sorted file with 1 million lines is less than 5%. + +2011-11-16 20:23:31 +0100 René Stadler + + * debug-viewer/GstDebugViewer/GUI/window.py: + window: connect action handlers using a function decorator + A bit esoteric, but better than maintaining the list of action names. + +2011-11-16 19:50:06 +0100 René Stadler + + * debug-viewer/GstDebugViewer/Common/GUI.py: + GUI: use 'with' statement + +2011-11-16 19:45:16 +0100 René Stadler + + * debug-viewer/GstDebugViewer/Common/Main.py: + * debug-viewer/GstDebugViewer/Common/utils.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/gst-debug-viewer: + Switch to new try..except syntax + This is forward compatible to Python 3. + +2011-11-06 13:35:26 +0100 René Stadler + + * debug-viewer/GstDebugViewer/GUI/columns.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + columns: auto size time column when setting base time + Base time formatting adds + or - in front of the timestamp, so the column has + to grow a little to not hide the last digit. + Also fixes a crash when setting the base time while the time column is hidden. + +2011-11-06 13:19:55 +0100 René Stadler + + * debug-viewer/GstDebugViewer/GUI/columns.py: + columns: also auto size thread and pid column on zoom change + +2011-11-06 13:18:19 +0100 René Stadler + + * debug-viewer/GstDebugViewer/GUI/columns.py: + columns: cleanup default size calculation + Some unused parameters here. + +2011-11-06 12:49:43 +0100 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Timeline: small cleanup + +2011-11-06 12:41:08 +0100 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/tests/create-test-log.py: + Data: remove log line serialization + This is incomplete and prone to error. Move it out into the utility script + (which is the only user). + +2011-11-06 12:19:52 +0100 René Stadler + + * debug-viewer/GstDebugViewer/GUI/colors.py: + * debug-viewer/GstDebugViewer/GUI/columns.py: + Remove odd-even row colors from log level column + This is more of visual clutter than aid. People also seem to be less likely to + spot the connection between the column and the timeline graph colors. + +2011-11-06 00:16:29 +0100 René Stadler + + * debug-viewer/GstDebugViewer/GUI/columns.py: + Resize time and log level columns after zoom change + +2011-11-05 23:47:47 +0100 René Stadler + + * debug-viewer/GstDebugViewer/GUI/app.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + Store zoom level in state + +2011-11-05 23:05:00 +0100 René Stadler + + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/data/menus.ui: + Add zoom reset action + +2011-11-05 22:53:24 +0100 René Stadler + + * debug-viewer/data/menus.ui: + Clean up context menu + These actions are not so commonly used, and also are not depending on the + context at all. + +2011-11-05 23:52:40 +0100 René Stadler + + * debug-viewer/GstDebugViewer/GUI/columns.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + Refactor and fix zoom handling + ColumnManager has to apply the zoom factor to newly added columns. Otherwise, + showing a previously hidden column appears with scale 1.0. + This also drops the value-changed signal emission for the vadjustment, as it is + apparently not needed. + +2010-07-06 11:42:08 +0300 Stefan Kost + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI/colors.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Add 'fixme' and 'trace' log levels + +2010-06-30 16:16:45 +0300 Stefan Kost + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + timeline: add tooltip to histogram as well + +2010-04-16 18:26:26 +0300 Stefan Kost + + * debug-viewer/GstDebugViewer/GUI/app.py: + * debug-viewer/GstDebugViewer/GUI/columns.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/data/menus.ui: + Add zoom in/out actions, reduce vertical row padding + Add two actions to shrink and enlarge the text in the log pane. Add a theme + overide to set expander size to 1 (see bug #615985) and also turn focus lines + off. Remove extra ypadding on cells. + +2011-09-25 21:38:48 +0200 René Stadler + + * debug-viewer/tests/test_models.py: + Fix tests + Forgot to convert this when modules got split. + +2011-09-11 21:10:47 +0100 Andrzej Bieniek + + * debug-viewer/GstDebugViewer/Main.py: + Fix --version option + +2011-09-09 22:02:28 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI/window.py: + Improve wording of hide lines actions + These also appear in the context menu of the timeline. The more generic wording + makes more sense for the timeline, since you do not pinpoint any specific line + in this case. + +2011-09-09 21:47:16 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + timeline: fix grey background artifact when enlarging window + +2011-09-07 16:11:58 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI/columns.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Fix glib warnings on model property access + Seems like pygobject can all of the sudden not handle a NULL model on a + property. Using the getter works around this. Also using the setter now for + consistency. + +2011-09-06 22:27:33 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + timeline: fix possible lag when dragging on timeline + I need to idle-aggregate scroll updates, since gtk performs heavy operations in + a synchronous fashion here (ironically, they do that to make scrolling smooth). + +2010-07-02 23:03:39 +0300 René Stadler + + * debug-viewer/GstDebugViewer/Common/GUI.py: + GUI: Work around GtkBuilder name property API break (gtk+ 2.20) + +2009-10-21 00:32:09 +0300 René Stadler + + * debug-viewer/setup.py: + setup.py: fix installation + +2009-10-21 00:31:46 +0300 René Stadler + + * debug-viewer/GstDebugViewer/Main.py: + Main: fix import + +2009-10-21 00:27:46 +0300 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Remove stale GUI module + +2009-10-16 21:45:29 +0300 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI/columns.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/data/menus.ui: + Add new base time feature + The log view context menu gains a new action "Set base time", which changes the + time column to show the delta to the selected row. + +2009-08-07 02:54:10 +0300 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/GUI/__init__.py: + * debug-viewer/GstDebugViewer/GUI/app.py: + * debug-viewer/GstDebugViewer/GUI/colors.py: + * debug-viewer/GstDebugViewer/GUI/columns.py: + * debug-viewer/GstDebugViewer/GUI/filters.py: + * debug-viewer/GstDebugViewer/GUI/models.py: + * debug-viewer/GstDebugViewer/GUI/window.py: + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Split giant GUI module into submodules + +2009-06-13 00:58:36 +0300 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + timeline: fix timestamp binary search + Fixes dragging the mouse over bigger gaps of log activity making the red + position rectangle come out next to the mouse pointer. Also selects the proper + row now, not randomly 1-2 rows before or after the gap. + +2009-06-12 21:53:28 +0300 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + GUI: remove unused imports + +2009-03-14 23:50:03 +0200 René Stadler + + * debug-viewer/setup.py: + setup.py: Fix version number + +2009-03-14 23:02:45 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Common/GUI.py: + * debug-viewer/GstDebugViewer/Common/Main.py: + * debug-viewer/GstDebugViewer/Common/utils.py: + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + * debug-viewer/setup.py: + * debug-viewer/tests/test_models.py: + Cleanup whitespace + +2009-03-14 20:40:52 +0200 René Stadler + + * debug-viewer/data/about-dialog.ui: + Update copyright statement in about dialog + +2009-03-14 20:06:16 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/data/menus.ui: + * debug-viewer/setup.py: + Rename UIManager file + +2009-03-14 20:03:37 +0200 René Stadler + + * debug-viewer/data/about-dialog.ui: + * debug-viewer/data/main-window.ui: + * debug-viewer/data/progress-dialog.ui: + Re-write builder files with glade3 + +2009-03-14 19:38:36 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Common/GUI.py: + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/__init__.py: + * debug-viewer/data/about-dialog.ui: + * debug-viewer/data/main-window.ui: + * debug-viewer/data/progress-dialog.ui: + * debug-viewer/setup.py: + Migrate from glade to GtkBuilder + +2009-03-11 00:41:26 +0200 René Stadler + + * debug-viewer/data/gst-debug-viewer.gladep: + Remove glade project file + +2008-11-29 21:06:52 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Common/Main.py: + Fix logging being on by default with recent Python + The fix for Python issue #1021 uncovered a mistake of mine. I was under the + impression that logging.NOTSET level means "off", but in fact it means to not + modify the level, and setting that on the root logger with basicConfig leads to + turning on all levels. + +2008-11-29 21:00:20 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + Use mmap in a portable way + +2008-11-26 23:21:57 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI.py: + Add FIXME comments + +2008-11-26 23:13:05 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + GUI: Fix edit-copy-line action crashing/copying wrong line + When the view was unfiltered, this crashed. When the view was range filtered, + this copied the wrong line. + Spotted by Stefan Kost. + +2008-11-05 00:00:48 +0200 René Stadler + + * debug-viewer/.bzrignore: + * debug-viewer/.gitignore: + Migrate .bzrignore -> .gitignore + +2008-06-30 19:48:34 +0300 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI.py: + Move more attribute lookups out of loops for speed + +2008-06-29 21:14:07 +0300 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + Add support for recent log format changes, be more tolerant on whitespaces, cope with object names containing '>' + +2008-06-13 22:58:54 +0300 René Stadler + + * debug-viewer/data/gst-debug-viewer.glade: + Use correct license in about dialog + +2008-03-02 15:24:16 +0200 René Stadler + + * debug-viewer/setup.py: + Fix installation by including missing packages + +2008-03-01 20:44:53 +0200 René Stadler + + * debug-viewer/data/gst-debug-viewer.svg: + Add (placeholder) SVG icon file + +2008-02-05 17:29:52 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Update vertical timeline when the widget size changes + +2008-02-04 17:36:57 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Fix bottom view showing the wrong selected log line + +2008-02-04 17:26:48 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Fix bottom view line activating the wrong line after filtering + +2008-01-25 15:44:38 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Fix bottom view not showing current line until you add something there + +2008-01-25 12:40:51 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Faster handling of partial expose events in timeline + +2008-01-25 11:17:02 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Main.py: + Mention GStreamer in --help output + +2008-01-25 11:12:48 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Allow to cancel a running filter process + +2008-01-24 16:19:15 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Make hide before/after action insensitive when first/last line is selected + +2008-01-24 15:18:37 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/data/gst-debug-viewer.ui: + Add filtering for object name and source code filename + +2008-01-24 14:29:39 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Add FIXME comment + +2008-01-24 12:16:41 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/data/gst-debug-viewer.ui: + Create own menu structure for log view context menu + +2008-01-24 11:49:41 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Sync show-timeline action state before connecting signal handler + +2008-01-24 11:47:27 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Timeline.py: Move per-window management into own class + +2008-01-24 11:12:05 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Re-format long line + +2008-01-24 10:59:14 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Replace gdk.ALL_EVENTS_MASK with proper minimal set of event flags + +2008-01-23 17:13:07 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/tests/performance.py: + Move performance test program into its own file + +2008-01-23 17:07:55 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/data/gst-debug-viewer.ui: + Allow creation of more than one window + +2008-01-23 17:07:51 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Add TODO comment + +2008-01-23 14:51:14 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Use GdkColors for level column, cleanup color handling + +2008-01-23 11:03:47 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Replace linear-time filtered index search with usage of bisect module + +2008-01-22 16:28:09 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Restore visible range of log view when changing filter + +2008-01-22 13:50:04 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Resolve small FIXME in SubRange + +2008-01-22 13:40:36 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Cleanup FilteredLogModel.super_model_changed_range + +2008-01-22 12:59:37 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/tests/test_models.py: + Fix crash with unparsable files + +2008-01-22 11:22:38 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Common/Main.py: + Make option parser work with glib before 2.13.2 + +2008-01-21 14:45:02 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + s/get_cells/get_cell_renderers/ again + +2008-01-21 13:24:02 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + Correctly parse categories with digits in them (fixes flump3dec, v4l2src messages) + +2008-01-21 11:15:42 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/tests/test_models.py: + Fix filtered range transformation (finally!) + +2008-01-11 11:11:00 +0200 René Stadler + + * debug-viewer/tests/test_models.py: + Add simple identity filter model tests + +2008-01-10 16:15:53 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/tests/test_models.py: + Fix filtered model index translation and improve tests + +2008-01-10 14:14:12 +0200 René Stadler + + * debug-viewer/tests/test_models.py: + Add test suite for filtered models + +2008-01-10 14:12:34 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Fix off-by-one error in filtered model range reclamping + +2008-01-10 13:49:58 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Improve filtered model interacting with range changes + +2008-01-02 20:54:33 +0100 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Unify two very similar methods + +2007-12-21 15:10:15 +0100 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Add FIXME about broken index translation logic + +2007-12-18 18:48:28 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Prevent crash with older bindings + +2007-12-18 17:10:08 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Fix more problems when clamping with filter turned on + +2007-12-18 15:26:05 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Fix off-by-one error causing display of spurious line when clamping with filter turned on + +2007-12-18 13:46:55 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Fix selection of line after changing filter, add logging + +2007-12-17 17:50:10 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Preserve clamped timestamp range when filtering and vice versa + +2007-12-13 13:43:28 +0200 René Stadler + + * debug-viewer/gst-debug-strip-color.py: + Fix color stripping script + +2007-12-13 13:36:45 +0200 René Stadler + + * debug-viewer/gst-debug-strip-color.py: + Add gst-debug-strip-color.py, a script to strip color codes + +2007-12-12 18:35:28 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Implement idle filtering (with progress display) + +2007-12-12 16:22:51 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/data/gst-debug-viewer.glade: + Factor out progress dialog handling into its own reusable object class + +2007-12-12 14:59:53 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Implement category filtering + +2007-12-12 13:49:02 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Allow for more than one (log level) filter to be set + +2007-12-11 11:38:45 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + Restore search to a consistent state when showing the search bar again + +2007-12-11 11:28:17 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + Add accelerators to search result navigation actions + +2007-12-11 11:16:44 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + Fix search result navigation action sensitivity when showing the find bar + +2007-12-11 11:13:46 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + Add search navigation menu items to view menu + +2007-12-11 10:44:20 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Common/GUI.py: + When right clicking to open a context menu, pass the event on (which selects the row) + +2007-12-10 17:40:31 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + Implement backward search result navigation + +2007-12-10 17:09:07 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + Remove dead/useless code + +2007-12-10 17:06:23 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + Rename variable + +2007-12-10 17:04:47 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + Implement lazy searching + +2007-12-10 14:22:51 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + Some search fixes + +2007-12-10 11:49:39 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + * debug-viewer/GstDebugViewer/Plugins/__init__.py: + Add status label to find bar + +2007-12-07 16:50:02 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Fix timeline warning/error indicator triangle vertical position + +2007-12-07 16:24:01 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Common/GUI.py: + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Add GUI utility function to add a popup menu to a widget + +2007-12-07 14:10:03 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Cleanup timeline warning/error triangle drawing, add TODOs + +2007-12-07 12:02:15 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Add tooltip to vertical timeline widget + +2007-12-07 11:25:30 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Add hide before/after menu items to timeline context menu + +2007-12-06 17:51:33 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/data/gst-debug-viewer.ui: + Add context menu to bottom view, with entry to clear all lines + +2007-12-04 16:34:53 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Fix copying of line to clipboard + +2007-12-04 16:21:45 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Remove implicit keybinding of copy message action + +2007-12-04 14:44:34 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Fix adding rows to the bottom view + +2007-12-04 14:40:41 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Fix crash when adding a line to bottom view with log filter turned on + +2007-12-04 14:35:50 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Implement filtered log model index translation + +2007-12-04 14:22:19 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Add support for stacking log model filters + +2007-12-03 17:49:04 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Do not let the user add duplicate lines to the bottom log view + +2007-12-03 17:44:40 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Keep the bottom view sorted by timestamp + +2007-12-03 16:38:29 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Unbreak filtering again + +2007-12-03 16:07:05 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + Improve method to update log view after search text change + +2007-12-03 15:47:58 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + Remove dead code + +2007-12-03 15:45:09 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI.py: + Use the mmapped fileobj in more places and use slice access + +2007-12-03 15:24:20 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Add simple cache eviction to LazyLogModel to limit memory usage + +2007-12-03 14:58:04 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + Rename show-find-bar action callback handler + +2007-12-03 12:18:23 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + Implement lazy highlighting of search results + +2007-12-03 11:46:44 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + Move search matching logic into the SearchOperation object + +2007-12-03 11:35:31 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + Add search result navigation + +2007-11-30 17:41:33 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Fix crash when showing all lines after having filtered down to zero lines + +2007-11-30 17:39:36 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Avoid GtkWarning when filtering down to no visible line at all + +2007-11-30 17:33:08 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Fix crash when displaying only one line + +2007-11-30 17:14:36 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Hide unimplemented filtering actions + +2007-11-30 17:13:12 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Add preliminary log level filtering support + +2007-11-30 16:44:36 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/data/gst-debug-viewer.ui: + Add level, category, object filtering actions + +2007-11-30 16:01:51 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Derive range/clamping model filter from the new base class + +2007-11-30 16:00:09 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Add identity filter model to save some memory + +2007-11-30 15:47:51 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Add base class for filtered log models + +2007-11-30 15:38:20 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Move class around + +2007-11-30 15:35:05 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Change code to cleaner terminology of filter model relationships + +2007-11-30 14:15:32 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Try to avoid a crash regarding illegal paths received from GtkTreeView + +2007-11-30 14:05:18 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Fix timeline level distribution plotting after gaps + +2007-11-30 10:54:32 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + For search highlighting, use pango attrlists instead of markup + +2007-11-30 10:31:45 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + After changing the filter, scroll to the selected row + +2007-11-30 10:21:38 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Retain bottom view model and fix crash after filter change + +2007-11-29 17:28:35 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Keep line selection when changing filter model + +2007-11-29 16:21:38 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Only auto size view columns once + +2007-11-29 15:49:58 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/data/gst-debug-viewer.glade: + Remove redundant property settings + +2007-11-29 15:40:51 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/data/gst-debug-viewer.glade: + Fix window size and position state persistency + +2007-11-29 15:34:35 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Fix hanging after loading an unparsable/colored file + +2007-11-29 15:25:31 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Behave a little better with unparsable/colored files + +2007-11-29 15:11:40 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Fix division by zero crash with unparsable/colored files + +2007-11-29 15:03:07 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + Fix crash when viewing a colored log file + +2007-11-29 14:29:10 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Fix crash when opening a file that has trash lines only + +2007-11-29 14:26:56 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + Gracefully handle garbage lines at the line cache level + +2007-11-29 14:07:31 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Correct wording in benchmark output + +2007-11-29 13:53:42 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Fix comment + +2007-11-29 13:51:46 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Fix level distribution calculation for the last partition + +2007-11-29 13:31:54 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Make message column receive a minimal size, which removes the size warning + +2007-11-29 13:26:54 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Remove more outdated comments + +2007-11-29 13:15:28 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Remove outdated comment + +2007-11-29 13:09:20 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Remove commented code + +2007-11-29 11:38:39 +0200 René Stadler + + * debug-viewer/data/gst-debug-viewer.ui: + Add separator to view menu + +2007-11-29 11:37:51 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + * debug-viewer/data/gst-debug-viewer.ui: + s/omit lines/hide lines/ + +2007-11-29 11:20:34 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + After load, select the first line + +2007-11-29 11:19:00 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + When navigating with the timeline, select the line in the center of the view + +2007-11-29 11:16:34 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + When navigating with the bottom view, select the target line + +2007-11-29 11:09:28 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + When activating a bottom view row, navigate the log view there + +2007-11-29 10:31:59 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Add ability to add rows to bottom view + +2007-11-28 16:10:57 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Avoid copies of the line index list in the line view model (fixes range filtering) + +2007-11-28 15:58:28 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Remove left over whitespace + +2007-11-28 15:32:06 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/data/gst-debug-viewer.glade: + Display timestamp and full message of selected line + +2007-11-28 11:27:26 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Common/GUI.py: + * debug-viewer/GstDebugViewer/GUI.py: + Correctly set sensitivity of row action group + +2007-11-28 10:57:02 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Add a (pretty limited) context menu to the timeline widget + +2007-11-28 10:42:46 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Adjust comment + +2007-11-28 10:27:45 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Common/Main.py: + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Main.py: + Fix handling of filename command line argument + +2007-11-28 09:56:35 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Tweak thread colors a bit + +2007-11-27 17:11:28 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Add fuzzy compatibility to unpatched pygtk 2.12.0 + +2007-11-27 16:50:41 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + * debug-viewer/data/gst-debug-viewer.ui: + Add ranged line omission feature + +2007-11-27 13:47:30 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + Speed up immediate search results by setting search start position + +2007-11-27 13:30:28 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Common/Data.py: + Use low idle priority for dispatching, to fix initial vtimeline display + +2007-11-27 12:03:32 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + Add basic search highlighting + +2007-11-26 18:01:30 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Draw vertical timeline connectors as triangles + +2007-11-26 16:55:11 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Set view selection mode to BROWSE + +2007-11-26 16:52:21 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Fix timeline position drawing and vertical timeline initial display + +2007-11-26 15:42:44 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Put basename of open file into window title + +2007-11-26 15:31:13 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/data/gst-debug-viewer.ui: + Add reload file functionality + +2007-11-26 14:55:31 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Disable interactive search on the log view + +2007-11-26 14:42:46 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Common/GUI.py: + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Plugins/FileProperties.py: + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + * debug-viewer/GstDebugViewer/Plugins/__init__.py: + Save state of timeline visibility + +2007-11-26 13:55:03 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Common/GUI.py: + * debug-viewer/GstDebugViewer/GUI.py: + Refactor state/config classes to be more flexible + +2007-11-26 11:06:31 +0200 René Stadler + + * debug-viewer/data/gst-debug-viewer.ui: + Comment out unimplemented new-window action item + +2007-11-26 10:53:37 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Gracefully handle errors when opening a file + +2007-11-26 09:47:53 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/FindBar.py: + * debug-viewer/data/gst-debug-viewer.glade: + Add very simple search bar + +2007-11-23 16:06:10 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Colorize vertical timeline lines to indicate different threads + +2007-11-23 15:04:14 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Add (commented) support to draw the vertical timeline on first display + +2007-11-23 11:46:43 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + * debug-viewer/data/gst-debug-viewer.glade: + Add vertical timeline widget (which looks quite cool) + +2007-11-22 20:44:02 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + s/get_cells/get_cell_renderers/ + +2007-11-22 16:27:34 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/data/gst-debug-viewer.ui: + Replace filename column with code column, listing filename and line number + +2007-11-22 16:06:55 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + * debug-viewer/GstDebugViewer/Plugins/__init__.py: + Make file->open work correctly + +2007-11-22 13:48:47 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Enable double-clicking a file in file chooser dialog + +2007-11-22 13:36:13 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Align log level column text in center + +2007-11-22 13:35:39 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Fix initial column size measurement + +2007-11-22 11:56:34 +0200 René Stadler + + * debug-viewer/tests/create-test-log.py: + Adjust test log generator + +2007-11-22 11:03:09 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Clamp timeline mouse position to actual range + +2007-11-22 10:47:06 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Common/Data.py: + * debug-viewer/GstDebugViewer/Common/GUI.py: + * debug-viewer/GstDebugViewer/Common/Main.py: + * debug-viewer/GstDebugViewer/Common/__init__.py: + * debug-viewer/GstDebugViewer/Common/utils.py: + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Main.py: + * debug-viewer/GstDebugViewer/Plugins/ColorizeRows.py: + * debug-viewer/GstDebugViewer/Plugins/FileProperties.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + * debug-viewer/GstDebugViewer/Plugins/__init__.py: + * debug-viewer/GstDebugViewer/__init__.py: + * debug-viewer/gst-debug-viewer: + * debug-viewer/setup.py: + Fix copyright/license headers and module docstrings + +2007-11-22 10:33:18 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Plugins/__init__.py: + Cleanup + +2007-11-22 10:29:23 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + * debug-viewer/GstDebugViewer/Plugins/__init__.py: + Display timeline by default + +2007-11-22 10:19:36 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Fix missing the last vertical ref line in the timeline display + +2007-11-22 09:56:21 +0200 René Stadler + + * debug-viewer/.bzrignore: + Add .bzrignore file + +2007-11-22 09:55:13 +0200 René Stadler + + * debug-viewer/data/gst-debug-viewer.glade.bak: + Kick glade backup file out of the repo + +2007-11-22 09:54:10 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/data/gst-debug-viewer.glade: + * debug-viewer/data/gst-debug-viewer.glade.bak: + Implement cancelling of the load process in the UI + +2007-11-22 09:31:37 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Progressively draw the debug level distribution into the timeline widget + +2007-11-21 17:40:31 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Common/Data.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Don't make timeline data processing block the GUI + +2007-11-21 15:21:40 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Common/__init__.py: + * debug-viewer/GstDebugViewer/GUI.py: + Ease importing of modules from the Common package + +2007-11-21 14:21:38 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + Ditch arrays for offset storage again + +2007-11-21 13:42:32 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Fix timeline for files where the first timestamp >> 0 + +2007-11-21 11:40:13 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + Use an array for line offset mapping (if file < 4GB) + +2007-11-21 10:47:40 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + Add LogLines class + +2007-11-20 17:45:35 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Rename COL_LINE to COL_LINE_NUMBER + +2007-11-20 17:40:35 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + Fix object name serialization + +2007-11-20 15:58:52 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/tests/create-test-log.py: + Add test script to generate a simple test log. Fix level name space adjustment + +2007-11-20 15:32:14 +0200 René Stadler + + * debug-viewer/MANIFEST.in: + * debug-viewer/po/POTFILES.in: + Add dummy po directory and add MANIFEST.in + +2007-11-20 15:25:32 +0200 René Stadler + + * debug-viewer/gst-debug-viewer: + * debug-viewer/gst-debug-viewer.desktop.in: + * debug-viewer/setup.cfg: + * debug-viewer/setup.py: + Copy over distutils setup from gst-inspector + +2007-11-20 14:52:26 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI.py: + Almost allow copying a full line to clipboard + +2007-11-20 13:58:34 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Remove commented code + +2007-11-20 13:56:15 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Replace model.get with model.get_value + +2007-11-20 13:34:00 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Cleanup + +2007-11-20 13:31:58 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Remove commented code, resolve FIXME + +2007-11-20 12:33:47 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Simplify function + +2007-11-20 11:06:27 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Rename more density stuff to timeline + +2007-11-19 15:55:08 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Remove unused attribute + +2007-11-19 15:52:01 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + And now make it actually run\! + +2007-11-19 15:44:54 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Also commit the previous change to the GUI module :-/ + +2007-11-19 15:27:16 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI.py: + Move final log line parsing from GUI to Data module + +2007-11-19 11:35:27 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + Optimize color stripping function a bit + +2007-11-19 10:59:52 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + Also plot green info line count in timeline display + +2007-11-17 10:23:58 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Add user/system time to benchmark output + +2007-11-17 10:06:09 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/Timeline.py: + * debug-viewer/data/gst-debug-viewer.ui: + Rename line frequency plugin/widget to timeline + +2007-11-16 17:25:08 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/LineFrequency.py: + Speed up level density sentinel + +2007-11-16 16:30:17 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI.py: + Correctly handle variable length thread address formatting + +2007-11-16 15:56:57 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/LineFrequency.py: + Add markers for warning and error log messages to the timeline display + +2007-11-16 15:06:59 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Plugins/LineFrequency.py: + Colorize log and debug log levels in frequency display widget + +2007-11-16 13:26:20 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Colorize debug level column + +2007-11-16 12:53:02 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI.py: + Rename debug level instances + +2007-11-16 12:48:08 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI.py: + Hacky commit to parse debug level on line cache level + +2007-11-16 11:03:22 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + Add (commented out) support for parsing debug level at line cache scan time + +2007-11-16 10:28:23 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Plugins/LineFrequency.py: + Cleanup + +2007-11-15 18:17:28 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Fix timestamps of unparsable lines to fix line density display + +2007-11-15 18:12:57 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI.py: + Fix message display + +2007-11-15 17:54:30 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI.py: + Save huge amounts of memory by never caching the message and interning data for the other columns + +2007-11-15 15:06:37 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI.py: + Use less regex matching to parse lines (does not provide a performance gain though) + +2007-11-15 14:07:00 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Make column id order match log line fields order + +2007-11-15 14:01:53 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Fix benchmark hack option + +2007-11-15 13:47:38 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/data/gst-debug-viewer.ui: + Add some simple filtering + +2007-11-15 09:20:34 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Add evil comment about treeview slowness with multiple selection mode + +2007-11-15 08:58:48 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Fix comment + +2007-11-14 22:51:47 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Use monospace font for some numeric columns + +2007-11-14 20:35:18 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Plugins/LineFrequency.py: + Also draw vertical help lines + +2007-11-14 16:56:35 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Change model design to be more filter friendly + +2007-11-14 15:49:03 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/LineFrequency.py: + Draw horizontal helper lines in frequency display + +2007-11-14 15:44:01 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Separate log model into base class and lazy implementation. Add basis for a filter model based on that + +2007-11-14 14:49:55 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Remove dead/commented out code + +2007-11-14 14:48:31 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Add/change comments + +2007-11-14 13:57:08 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Change view columns menu item label + +2007-11-14 13:34:53 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/data/gst-debug-viewer.ui: + Add PID column + +2007-11-14 13:15:36 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/FileProperties.py: + * debug-viewer/data/gst-debug-viewer.ui: + Add skeleton for file properties plugin + +2007-11-14 12:48:43 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/data/gst-debug-viewer.ui: + Add debug output. Add filename column + +2007-11-14 11:31:57 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Add view columns visibility and ordering state persistence + +2007-11-14 11:13:07 +0200 René Stadler + + * debug-viewer/GstDebugViewer/GUI.py: + Don't make column headers clickable + +2007-11-14 10:55:12 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI.py: + Fix progress display on load + +2007-11-14 10:44:08 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Plugins/LineFrequency.py: + If the indicator in the frequency display is just 1px wide, don't use transparency + +2007-11-14 10:30:19 +0200 René Stadler + + * debug-viewer/GstDebugViewer/Common/Data.py: + * debug-viewer/GstDebugViewer/Common/GUI.py: + * debug-viewer/GstDebugViewer/Common/Main.py: + * debug-viewer/GstDebugViewer/Common/__init__.py: + * debug-viewer/GstDebugViewer/Common/utils.py: + * debug-viewer/GstDebugViewer/Data.py: + * debug-viewer/GstDebugViewer/GUI.py: + * debug-viewer/GstDebugViewer/Main.py: + * debug-viewer/GstDebugViewer/Plugins/ColorizeRows.py: + * debug-viewer/GstDebugViewer/Plugins/LineFrequency.py: + * debug-viewer/GstDebugViewer/Plugins/__init__.py: + * debug-viewer/GstDebugViewer/__init__.py: + * debug-viewer/data/gst-debug-viewer.glade: + * debug-viewer/data/gst-debug-viewer.glade.bak: + * debug-viewer/data/gst-debug-viewer.gladep: + * debug-viewer/data/gst-debug-viewer.png: + * debug-viewer/data/gst-debug-viewer.ui: + * debug-viewer/gst-debug-viewer.desktop: + * debug-viewer/gst-debug-viewer.py: + * debug-viewer/pixmaps/gst-debug-viewer.png: + New import (the old repo got busted, just had 4 revs anyways) + +2014-09-06 12:34:39 +0200 Thibault Saunier + + * validate/configure.ac: + * validate/docs/Makefile.am: + validate: Make sphinx documentation generation optionnal + +2014-09-06 11:41:48 +0200 Thibault Saunier + + * validate/configure.ac: + * validate/docs/Makefile.am: + * validate/docs/launcher/Makefile.am: + * validate/docs/launcher/conf.py: + * validate/docs/launcher/index.rst: + * validate/docs/launcher/launcher.rst: + * validate/docs/launcher/modules.rst: + validate:launcher: Add needed files to build documentation with sphinx + +2014-09-06 11:38:38 +0200 Thibault Saunier + + * validate/tools/launcher/apps/Makefile.am: + * validate/tools/launcher/apps/geslaunch.py: + * validate/tools/launcher/apps/gstvalidate.py: + validate: launcher: Cleanup and rename apps to avoid '-' in their name + +2014-09-06 10:02:13 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate:launcher: Avoid '.' before media file extension in test classnames + +2014-09-05 19:47:00 +0000 Felix Schwarz + + * validate/docs/validate-design.txt: + * validate/docs/validate-usage.txt: + * validate/docs/validate/command-line-tools.xml: + * validate/docs/validate/envvariables.xml: + * validate/docs/validate/scenarios.xml: + validate:docs: fix spelling mistakes + https://bugzilla.gnome.org/show_bug.cgi?id=736160 + +2014-09-05 23:15:29 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: Take a const gchar ** in gst_validate_print_action_types + This is what we actually need and thus is cleaner. + +2014-09-05 23:03:58 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: Implement the notion of implementer namespace to the action types + This allows users to know who implements an action type. + + Enhance the printing of all action making it readable. + +2014-09-05 19:30:52 +0200 Thibault Saunier + + * validate/tools/gst-validate.c: + validate: Add informations on the switch-track action overrided for playbin + +2014-09-04 23:54:34 +0200 Thibault Saunier + + * validate/docs/validate/Makefile.am: + * validate/docs/validate/command-line-tools.xml: + * validate/docs/validate/envvariables.xml: + * validate/docs/validate/gst-validate-docs.sgml: + * validate/docs/validate/scenarios.xml: + * validate/gst/validate/gst-validate-scenario.c: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: docs: Add some GstValidate usage documentation + + Fix minor issues in the gst-validate and gst-validate-transcoding + tools documentation + +2014-09-04 11:54:41 +0200 Guillaume Desmottes + + * validate/gst/validate/gst-validate-monitor-factory.c: + validate: remove redundant pre-condition in monitor_factory_create + The same check is already done at the head of the function. + https://bugzilla.gnome.org/show_bug.cgi?id=736019 + +2014-09-04 11:53:56 +0200 Guillaume Desmottes + + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-runner.c: + validate: fix a couple of typos in comments + https://bugzilla.gnome.org/show_bug.cgi?id=736019 + +2014-09-04 19:18:25 +0200 Thibault Saunier + + * validate/docs/validate-usage.txt: + validate:docs: Sensibly update the usage file + +2014-08-22 19:30:14 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Use a GList to store action types instead of hashtable + It is more adapted and allows us to print the action types in a stable + maneer. + +2014-08-22 18:45:13 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + validate: report: Do not repeat type name when printing its details + +2014-08-19 11:10:57 +0200 Thibault Saunier + + * validate/configure.ac: + validate: Change the version to 1.0.0.1 + The 1.0.0.1 means that it is targetting the GStreamer 1.X serie, + and is a git version (thus 0.1) + GstValidate will most probably not be released and we should try to + be able to use it with as many version of the GStreamer 1.X serie + as possible. + +2014-08-18 18:41:50 +0200 Thibault Saunier + + * validate/Makefile.am: + * validate/configure.ac: + * validate/docs/Makefile.am: + * validate/docs/validate/Makefile.am: + * validate/docs/validate/gst-validate-docs.sgml: + * validate/docs/validate/gst-validate-sections.txt: + * validate/docs/validate/gst-validate.types: + * validate/docs/version.entities: + * validate/docs/version.entities.in: + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-monitor-factory.c: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/gst/validate/validate.c: + validate: Document the API with gtk-doc + +2014-06-11 09:23:11 +0200 Thibault Saunier + + * validate/gst/validate/Makefile.am: + validate: Add GObject Introspection support + +2014-08-14 10:55:44 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: Rework the action parameter API + Making it possible to properly define parameters, and describe them. + + Document all action types! + +2014-08-13 23:07:47 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/gst/validate/validate.c: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: Add an option to print all avalaible actions with details + + Cleanup actions descriptions + + Make GstValidateActionType internal only and only expose the structure + +2014-08-14 10:57:33 +0200 Thibault Saunier + + * validate/tools/gst-validate.c: + validate: Use the buffering mode to see if pipeline is live or not + +2014-08-14 10:56:56 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + validate: Do not segfault when receiving a segment on unlink pad + For some reason we did no discover that before. + +2014-08-13 20:47:24 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate: Make GstValidateActionType a GstMiniObject and expose it in the API + +2014-08-13 20:46:17 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario: Cleanup header and add some padding to classes + Let's start making gst-validate ABI and API stable + +2014-08-20 18:59:26 +0530 Anuj Jaiswal + + * validate/tools/gst-validate.c: + gst-validate: fix some minor memory leaks + https://bugzilla.gnome.org/show_bug.cgi?id=735099 + +2014-08-19 18:06:14 +0200 Mathieu Duponchelle + + * validate/tools/launcher/apps/gst-validate.py: + validate: generate test names with the stream_info filename. + And not with the contained uri string, which is variable. + +2014-08-12 15:14:28 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: The scenario only old a weak ref so unref the weak ref + We were unrefing an object we did not actually own a ref on. + +2014-08-12 09:36:34 +0200 Thibault Saunier + + * validate/tools/gst-validate.c: + validate: Print when we set pipeline state because of buffering + +2014-08-11 20:19:02 +0200 Thibault Saunier + + * validate/configure.ac: + * validate/tools/gst-validate-launcher.in: + * validate/tools/launcher/apps/Makefile.am: + * validate/tools/launcher/apps/validate/Makefile.am: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/main.py: + validate:launcher: Properly handle libsdir when gst-validate is installed + + Fix the _in_devel function + + Install the validate default testsuite implementation in the right + place + +2014-08-11 13:21:09 +0200 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + validate:launcher: Let testsuite know the actual file in which they are + +2014-08-11 13:19:22 +0200 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + validate:launcher: Add the logic of needed env variables in tests + +2014-08-10 12:41:57 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate:launcher: Expose all classes to be used to create testsuites + To create testsuite from outside gst-validate, the user will need to be + able to use the TestGenerator and subclasses of Test that we implement + in the apps, to do so we publicly expose them in the TestManager class + so that user have acces to everything they need. + +2014-08-10 12:04:31 +0200 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/httpserver.py: + * validate/tools/launcher/main.py: + * validate/tools/launcher/reporters.py: + validate:launcher: Handle stdout/stderr as possible logfiles + Allowing people to get all the logs in the terminal + +2014-08-09 23:22:39 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + validate: Allow several outputs in GST_VALIDATE_FILE + +2014-08-09 16:34:09 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/utils.py: + validate: Launcher: Add support for the dash protocol + And make sure that the HTTP server is started if it is needed to serve + some HLS or DASH streams + +2014-08-08 19:14:02 +0200 Thibault Saunier + + * validate/tools/launcher/main.py: + validate:Launcher: Use the first media path as a path for http server + We need to have a default path and the first one sounds like a + reasonnable default. + +2014-08-08 12:33:54 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-bin-monitor.h: + * validate/tools/launcher/RangeHTTPServer.py: + * validate/tools/launcher/httpserver.py: + * validate/tools/launcher/main.py: + validate:launcher: Allow limitating local HTTP server bandwith + By default we limit its bandwith to 1MBps which is somehow similare to a + good internet connection case. + +2014-08-05 18:51:20 +0200 Thibault Saunier + + * validate/data/switch_audio_track_while_paused.scenario: + * validate/tools/launcher/apps/validate/validate_testsuite.py: + * validate/tools/launcher/baseclasses.py: + validate:launcher: Disable racy HLS tests + + Add need-clock-synk to switch_audio_track_while_paused as it relies on + the clock sync to pause and then display subtitles + +2014-08-05 10:59:21 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/apps/validate/validate_testsuite.py: + validate:launcher: Take into account exitcode in transcoding tests + And disable a few racy tests that were not detected because of that + +2014-07-29 12:17:21 +0200 Thibault Saunier + + * validate/tools/launcher/apps/validate/validate_testsuite.py: + validate:launcher:testsuite: De activate backward playback where appropriate + And re activate it where it works + +2014-07-26 11:42:09 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Actually accept rounding errors and small mistakes for position + WHen seeking in paused the position right after should be pretty much + the exact one, but sometimes it can be a little different because of + rounding issues and similare. + +2014-07-26 11:41:09 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-runner.h: + validate: Add a way to avoid printing all the issue in reports + Avoiding user to be flooded by information he does not want while + debugging + +2014-07-26 08:27:55 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-reporter.c: + validate: Print the report when aborting because of an issue + Letting a chance to the user to know what bug he faced! + +2014-07-24 19:26:29 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-scenario.c: + validate: Enhance output about critical errors + +Lower some warning to INFO + +2014-07-24 19:02:38 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/gst-validate-override-registry.h: + * validate/gst/validate/gst-validate-override.c: + * validate/gst/validate/gst-validate-scenario.c: + validate: Allow overrides for scenario issues + +2014-07-21 18:00:42 +0200 Thibault Saunier + + * validate/tools/launcher/reporters.py: + validate: Avoid readding several time the same test in the tests result list + +2014-07-19 11:47:44 +0200 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + validate:launcher: Fix test number print + +2014-07-31 17:54:17 +0200 Thibault Saunier + + * validate/tools/launcher/apps/validate/validate_testsuite.py: + tools: Launcher: Disable validate.file.*.simple.scrub_forward_seeking.synchronized + It is still a bit racy and sometimes the seek just does not happen + +2014-07-31 17:43:51 +0200 Thibault Saunier + + * validate/tools/launcher/apps/validate/validate_testsuite.py: + tools: launcher: Disable subtitle track switching scenario on Sintel + It is racy at the moment. + +2014-07-23 20:39:05 +0200 Thibault Saunier + + * validate/data/change_state_intensive.scenario: + * validate/data/fast_backward.scenario: + * validate/data/fast_forward.scenario: + * validate/tools/launcher/baseclasses.py: + validate:launcher: Allow informing minimum media duration in scenarios + Allowing the launcher to avoid running tests on medias that are not long + enough + +2014-07-23 17:49:21 +0200 Thibault Saunier + + * validate/tools/launcher/main.py: + * validate/tools/launcher/reporters.py: + validate:launcher: Always print final report + enhance output + +2014-07-23 14:51:43 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Properly check that remaining actions are not 'ending' ones + When checking that all action were executed, we need to make sure that + actions such as EOS or stop are not taken into account as we might have + shorter medias than the duration of the scenario, and that should not be + fatal. + + Plug a leak on the way + +2014-07-23 14:43:29 +0200 Thibault Saunier + + * validate/data/change_state_intensive.scenario: + * validate/data/fast_backward.scenario: + * validate/data/fast_forward.scenario: + * validate/data/seek_backward.scenario: + * validate/data/seek_forward.scenario: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + validate: launcher: Force clock syncronization for some scenarios + In some cases it is necessary that the clock is sync so that all the + actions can be executed. + +2014-07-23 10:54:37 +0200 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/main.py: + validate: Launcher: Make sure tests are always executed in same order + +2014-07-22 11:42:48 -0300 Thiago Santos + + * validate/data/camerabin_signal.scenario: + * validate/gst/validate/gst-validate-scenario.c: + gst-validate-scenario: add emit-signal + emit-signal action allows to emit signals to elements in scenarios. + The implementation only accepts signals without arguments for now but + it can be extended to use parameters if needed in the future + +2014-07-22 15:49:09 +0200 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + validate: Launcher: Fix a backtrace using self in a @staticmethod + +2014-07-21 22:41:28 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-scenario.c: + gst-validate-scenario: the structure has the type + Get the GValue directly from the structure and do not assume everything + is stored as a string and use the GstStructure's GValue to set the property + to the instances + +2014-07-21 22:01:27 -0300 Thiago Santos + + * validate/tools/gst-validate.c: + gst-validate: properly set pipeline to null before unref + In case it fails when going ready->paused it will remain in ready state + and be unref'd in ready, leading to an assertion + +2014-07-21 19:09:24 +0200 Arnaud Vrac + + * validate/configure.ac: + * validate/gst/validate/Makefile.am: + validate: Fix build on some custom platforms + We need to explicitely pass GLIB_LIBS for GModule as it seems not to be included by + GST_ALL_LIBS and we need LIBM + +2014-05-19 18:06:46 +0200 Lubosz Sarnecki + + * validate/tools/launcher/httpserver.py: + httpserver: launch webserver with the same python interpreter. + +2014-07-19 09:48:17 +0200 Thibault Saunier + + * validate/tools/gst-validate.c: + validate: Dot the pipeline on interuption + +2014-07-18 15:57:24 +0200 Aurélien Zanelli + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + validate: duplicate strings in gst_validate_issue_new() + Do this to avoid discarding 'const' qualifier when using it with + constant strings. Moreover it will avoid a g_free on constant string. + https://bugzilla.gnome.org/show_bug.cgi?id=733362 + +2014-07-18 16:28:49 +0200 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + validate:launcher: Fix a backtrace using an undefined method + +2014-07-17 16:48:21 +0200 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + valdate:launcher: Do not refer to self in @staticmethod + There is no self in there. + +2014-07-17 16:44:08 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Add a set-debug-threshold scenario action + Allowing users to activate the debug only at the interesting time + +2014-07-17 16:42:02 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + validate: Add Gst debugging when using gst-validate printing feature + Giving usefull debugging informations in the GSt debug logs + +2014-07-17 12:17:31 +0200 Thibault Saunier + + * validate/tools/gst-validate.c: + validate: Do not auto flush pipeline bus + We want to see all messages in our async handler + And flush it when we are done. + +2014-07-16 19:38:01 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate:launcher: Avoid using sync=true on fakesinks + Making the test run much faster! + +2014-07-16 19:37:35 +0200 Thibault Saunier + + * validate/tools/launcher/reporters.py: + validate: Launcher: Print total time spent in the final report + +2014-07-16 18:21:16 +0200 Thibault Saunier + + * validate/data/switch_subtitle_track_while_paused.scenario: + validate: Make switch_subtitle_track_while_paused handle states + +2014-07-16 14:46:32 +0200 Thibault Saunier + + * validate/data/disable_subtitle_track_while_paused.scenario: + vaildate: Make disable_subtitle_track_while_paused handle states + +2014-07-13 18:21:50 +0200 Thibault Saunier + + * validate/data/scrub_forward_seeking.scenario: + validate: Make scrub_forward_seeking handle states + +2014-07-16 13:54:54 +0200 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + validate:launcher: Properly check that encoded files have the exact wanted format + +2014-07-16 12:50:41 +0200 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + validate:launcher: Add a method to create a GstValidateMediaDescriptor from a uri + +2014-07-16 12:16:03 +0200 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/utils.py: + validate:launcher: Move MediaFormatCombination to baseclasses.py + + Add some simple helpers + +2014-07-16 12:03:14 +0200 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/utils.py: + validate:launcher: Implement a GstValidateEncodingTestInterface class + Allowing code to be shared between apps that run rendering tests + +2014-07-16 11:39:08 +0200 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + validate:launcher: Implement a MediaDescriptor subclass for xges project files + +2014-07-16 11:36:29 +0200 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + validate:launcher: Make a MediaDescriptor baseclass to be used by any application + +2014-07-16 10:35:34 +0200 Thibault Saunier + + * validate/tools/launcher/main.py: + validate:launcher: Give information to users when cloning asset failed + It might not be obvious from the stacktrace so it is better to clearly + explain what the failure was when we know it + +2014-07-16 10:16:19 +0200 Thibault Saunier + + * validate/tools/launcher/main.py: + validate:launcher: Fixup the default asset update command + +2014-07-16 10:12:04 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/utils.py: + validate:launcher: Generate proper EncodingProfiles for audio/video only media files + +2014-07-16 10:10:44 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + validate:launcher: Move the MediaDescriptor class to the baseclasses.py file + +2014-07-16 10:09:32 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate:launcher: Allow transcoding audio only files 5 time longer than long_limit + Transcoding audio is a lot shorter so we can concider that transcoding files that are only + only is 5 time shorter than the actual file (empirical number) + +2014-07-16 10:03:11 +0200 Thibault Saunier + + * validate/tools/gst-validate-media-check.c: + validate:media-check: Pass the GError where needed. + +2014-07-15 12:16:34 +0200 Thibault Saunier + + * validate/gst/validate/media-descriptor-writer.c: + validate: Avoid segfault in the error path + +2014-07-15 11:59:23 +0200 Thibault Saunier + + * validate/gst/validate/media-descriptor-writer.c: + validate:media-descriptor-writer: Handle medias with 1 single stream + +2014-07-08 13:50:11 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Check that after a seek in PAUSED position is perfect + In case of ACCURATE seeking, the position after a SEEK in PAUSED state + should be *exactly* the one requested by the user. + +2014-07-11 15:45:18 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-scenario.c: + scenario: add set_property scenario action + Allows setting element's properties during a scenario. Very useful + for testing that elements behave correctly when changing properties + during playing state + https://bugzilla.gnome.org/show_bug.cgi?id=733070 + +2014-07-09 19:10:57 +0300 Sreerenj Balachandran + + * codecanalyzer/.gitignore: + * codecanalyzer/AUTHORS: + * codecanalyzer/COPYING: + * codecanalyzer/Makefile.am: + * codecanalyzer/NEWS: + * codecanalyzer/README.md: + * codecanalyzer/autogen.sh: + * codecanalyzer/configure.ac: + * codecanalyzer/data/Makefile.am: + * codecanalyzer/data/pixmaps/Makefile.am: + * codecanalyzer/data/pixmaps/codecanalyzer-logo.png: + * codecanalyzer/data/pixmaps/frame-thumbnail.png: + * codecanalyzer/data/ui/LICENSE.txt: + * codecanalyzer/data/ui/Makefile.am: + * codecanalyzer/data/ui/mainwindow.xml: + * codecanalyzer/data/ui/menu.xml: + * codecanalyzer/src/Makefile.am: + * codecanalyzer/src/codecanalyzer.c: + * codecanalyzer/src/gst_analyzer.c: + * codecanalyzer/src/gst_analyzer.h: + * codecanalyzer/src/plugins/Makefile.am: + * codecanalyzer/src/plugins/gst/Makefile.am: + * codecanalyzer/src/plugins/gst/analyzersink/Makefile.am: + * codecanalyzer/src/plugins/gst/analyzersink/analyzer_utils.c: + * codecanalyzer/src/plugins/gst/analyzersink/analyzer_utils.h: + * codecanalyzer/src/plugins/gst/analyzersink/gstanalyzersink.c: + * codecanalyzer/src/plugins/gst/analyzersink/gstanalyzersink.h: + * codecanalyzer/src/plugins/gst/analyzersink/mpeg_xml.c: + * codecanalyzer/src/plugins/gst/analyzersink/mpeg_xml.h: + * codecanalyzer/src/plugins/gst/analyzersink/plugin.c: + * codecanalyzer/src/plugins/gst/analyzersink/xml_utils.c: + * codecanalyzer/src/plugins/gst/analyzersink/xml_utils.h: + * codecanalyzer/src/xml_parse.c: + * codecanalyzer/src/xml_parse.h: + New Tool: Add a CodecAnalyzer + https://bugzilla.gnome.org/show_bug.cgi?id=731853 + +2014-07-07 16:12:22 +0200 Stefan Sauer + + * mediainfo/TODO: + * mediainfo/src/mi-info.vala: + TODO: add some planning comments + +2013-10-22 10:57:14 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: add wikilink for opus + +2014-07-02 17:53:55 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Execute actions if we get seeked in ready state + +2014-07-02 11:27:22 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/tools/gst-validate.c: + validate: Let scenarios tell the apps about whether it handles states + The user only needs to add handles-states=true in the description line + of the scenario + +2014-06-19 12:58:49 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Do not care about the position if we are not at least in PAUSED state + At that time the position query will be meaningless so we should just go to the next + action. + +2014-06-26 15:07:39 +0200 Thibault Saunier + + * validate/tools/launcher/apps/validate/validate_testsuite.py: + validate: Handle MXF files + +2014-06-26 15:03:07 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/media-descriptor.c: + validate:media-check: Changes in tags detection are not fatal issues + +2014-06-26 13:01:13 +0200 Thibault Saunier + + * validate/tools/launcher/apps/Makefile.am: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/apps/validate/validate_testsuite.py: + validate:launcher Add video mixing tests + + Move default_testsuite.py to validate_testsuite.py as we are now + exposing tests that are not enabled by default + +2014-06-26 12:42:38 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/apps/validate_default_testsuite.py: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/main.py: + validate:launcher: Move the notion of test generator to the baseclasses + This can be very usefull for all the TestManager and thus exposes a + higher level API for test writers. + +2014-06-19 16:26:43 +0200 Thibault Saunier + + * validate/tools/launcher/apps/Makefile.am: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/apps/validate_default_testsuite.py: + validate:launcher: Move default testsuite to a dedicated file + Making the separation cleaner between the launcher and the test + implementation + +2014-06-20 19:01:41 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Properly handle CLOCK_TIME_NONE position and duration values + In the value parser. + +2014-06-19 13:03:48 +0200 Thibault Saunier + + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: Print the return value at the end + Making it easier to know whether the test passed or not. + +2014-06-19 12:56:34 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Move the check about whether we are still seeking upper in the function + Avoiding to try to get position and do operations on a pipeline that is seeking + +2013-11-25 13:55:10 +0000 Vincent Penquerc'h + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: only use valid position/duration + Position/duration query may fail, or yield unknown values (eg, + unknown duration for live streams). In these cases, we must ensure + we do not use those invalid values. + https://bugzilla.gnome.org/show_bug.cgi?id=715160 + +2014-06-19 09:38:52 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate:launcher: Not concider all scenarios by default with --wanted-test + Instead let the users activate that with -t ALL + +2014-06-19 09:22:36 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/main.py: + validate:launcher: Let the user set user options in the config file + +2014-06-18 17:27:09 +0200 Thibault Saunier + + * validate/tools/launcher/main.py: + validate:launcher: Use RawTextHelpFormatter to (not) format user help + +2014-06-18 17:26:05 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/main.py: + validate:launcher: Add a way to create test suite outside the three + + Make sure to namespace the API + + Remove cruft about G_V_PROTOCOL_VIDEO_RESTRICTION_CAPS + +2014-06-18 13:02:53 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: We are not changing state if the set_state failed. + +2014-06-18 13:02:29 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Add a debug category and add some debug + +2014-06-18 13:01:42 +0200 Thibault Saunier + + * validate/gst/validate/media-descriptor-writer.c: + validate: media-descirptor: Add more infos about discoverer error + +2014-06-18 12:51:02 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate:launcher: Cleanup the way we generate tests adding the notion of TestGenerator + Making it easier to extend the testsuite. + +2014-06-18 15:57:14 +0200 Thibault Saunier + + * validate/tools/gst-validate.c: + validate:tools: Dot the pipeline on usefull places + Meaning on warning and state changes. + +2014-06-28 12:33:45 +0200 Sebastian Dröge + + * validate/gst/validate/gst-validate-reporter.c: + validate: Don't call gst_debug_log_valist() if debugging is disabled + And also stop leaking a string every time. + +2014-06-28 11:36:27 +0200 Sebastian Dröge + + * validate/tools/Makefile.am: + gst-validate: Add $(GIO_LIBS) and $(GIO_CFLAGS) as required + +2014-06-17 15:10:41 +0200 Thibault Saunier + + * validate/tools/gst-validate-launcher.in: + validate: Fix launcher when running installed + +2014-06-17 14:17:21 +0200 Thibault Saunier + + * validate/data/Makefile.am: + validate: scenarios: Install play_15s.scenario + +2014-06-16 16:47:18 +0200 Thibault Saunier + + * validate/tools/launcher/reporters.py: + validate:launcher:reporter: Sort Final report by results + +2014-06-16 16:46:21 +0200 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/utils.py: + validate:launcher:ges: Fix rendered duration checking + +2014-06-16 16:40:10 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + validate: pad-monitor: Do not compare not fixed sinkpad caps fields + We are only able to check that the sink pad caps values are inside the src pad + value. + +2014-06-16 08:49:22 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Do not be so tolerant about seek drift + +2014-06-03 09:38:29 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: Handle out-of-segment first buffer + If the initial buffer is before segment.start, we don't want to raise + the "first buffer doesn't have 0 running-time" issue. + Also add debug for tracking issues + +2014-06-03 10:02:10 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-media-info.c: + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/media-descriptor.c: + * validate/tools/gst-validate-media-check.c: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: Run gst-indent on all code + so whitespace. much indent. spacing ! + +2014-05-27 12:30:54 +0200 Thibault Saunier + + * validate/configure.ac: + validate: Depend at least on GLib 2.36 + +2014-05-19 19:42:46 +0200 Thibault Saunier + + * validate/data/Makefile.am: + * validate/data/scrub_backward_seeking.scenario: + * validate/tools/launcher/apps/ges-launch.py: + validate: Add a scrub_backward_seeking scenario + + Make use of it in ges-launch and do not try to seek while playing in + GES as it is not supported yet + +2014-05-24 01:28:36 -0400 Nicolas Dufresne + + * validate/gst/validate/gst-validate-scenario.c: + validate: Don't pass NULL to g_strsplit + +2014-05-22 16:13:31 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-utils.c: + validate-utils: Fix unitialized variable + gst-validate-utils.c:413:7: error: variable 'v0' is used uninitialized whenever 'if' condition is true [-Werror,-Wsometimes-uninitialized] + if (c == '!') { + ^~~~~~~~ + gst-validate-utils.c:424:10: note: uninitialized use occurs here + return v0; + ^~ + gst-validate-utils.c:413:3: note: remove the 'if' if its condition is always false + if (c == '!') { + ^~~~~~~~~~~~~~~ + gst-validate-utils.c:411:13: note: initialize the variable 'v0' to silence this warning + gdouble v0; + ^ + = 0.0 + 1 + +2014-05-21 11:50:09 +0200 Thibault Saunier + + * validate/configure.ac: + * validate/gst/Makefile.am: + * validate/gst/overrides/Makefile.am: + * validate/gst/overrides/gst-validate-default-overrides.c: + * validate/gst/preload/Makefile.am: + * validate/gst/preload/gst-validate-monitor-preload.c: + * validate/gst/validate/Makefile.am: + validate: Move overrides and preload libraries to dedicated folders + This way it is cleaner and it is simpler to handle the various compilation dependencies. + +2014-05-16 16:20:26 +0200 Lubosz Sarnecki + + * validate/tools/gst-validate-launcher.in: + * validate/tools/launcher/RangeHTTPServer.py: + * validate/tools/launcher/__init__.py: + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/httpserver.py: + * validate/tools/launcher/main.py: + * validate/tools/launcher/reporters.py: + * validate/tools/launcher/utils.py: + python: change shebangs to python2 + +2014-05-15 09:46:24 +0200 Thibault Saunier + + * validate/gst/validate/media-descriptor-parser.c: + * validate/gst/validate/media-descriptor-writer.c: + validate: Properly use boolean in XML + +2014-05-08 17:48:39 +0200 Thibault Saunier + + * validate/gst/validate/media-descriptor-writer.c: + validate: Improve perf when writing the XML file + + Pass the file into gst-indent + +2014-05-07 13:14:51 +0200 Thibault Saunier + + * validate/data/Makefile.am: + * validate/data/change_state_intensive.scenario: + * validate/tools/launcher/apps/gst-validate.py: + validate: Add a scenario that switches state many intensively + + Use it by default in the launcher tests + +2014-05-07 12:43:53 +0200 Thibault Saunier + + * validate/tools/launcher/main.py: + validate: Minor fix for blacklisted test output formatting + +2014-05-07 12:21:49 +0200 Thibault Saunier + + * validate/tools/launcher/main.py: + validate:launcher: Add an option to only launch the http server + +2014-05-07 12:21:30 +0200 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + validate: Keep scenario discovering logs in a file + +2014-05-07 11:34:47 +0200 Thibault Saunier + + * validate/data/fast_forward.scenario: + validate: Avoid using stop value in the fast_forward scenario + +2014-05-07 11:30:39 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate: Properly check that outputed videos have a correct duration + +2014-05-07 11:30:09 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/main.py: + * validate/tools/launcher/utils.py: + validate: Add the notion of "long" tests so that we can avoid some test to be run if they are too long + +2014-05-07 09:51:19 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Add a general action to set state + +2014-05-07 09:50:28 +0200 Thibault Saunier + + * validate/autogen.sh: + validate: Properly set the pre commit hook + +2014-05-07 09:46:28 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Pass into gst-indent + +2014-05-07 09:15:34 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: The wait mandatory field is duration + +2014-05-07 09:11:12 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Make sure mandatory fields are present when parsing scenarios + +2014-05-06 15:34:08 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-bin-monitor.h: + * validate/gst/validate/gst-validate-scenario.c: + scenarios: add a stateless property. + This property enables the user to have actions executed independently + of the state of the pipeline. + Conflicts: + validate/gst/validate/gst-validate-scenario.c + +2014-05-05 17:00:45 +0200 Mathieu Duponchelle + + * validate/gst/validate/gst-validate-scenario.c: + scenario: make sure to not execute actions when changing state. + Conflicts: + validate/gst/validate/gst-validate-scenario.c + +2014-05-04 09:30:14 +0200 Thibault Saunier + + * validate/tools/gst-validate.c: + validate: The 'buffering' variable needs to be static + We need its value between bus_callback calls to be the same + +2014-05-02 17:25:07 -0400 Luis de Bethencourt + + * validate/tools/gst-validate.c: + gst-validate: some static variables can be local + buffering is only used inside the bus_callback, so it can have that local + scope. same thing with ret which is only used in the main function. + +2014-05-02 16:53:51 -0400 Luis de Bethencourt + + * validate/tools/gst-validate.c: + gst-validate: small typo in usage summary + +2014-05-02 20:05:28 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Wait for the PAUSED state to be reached before executing actions + +2014-05-02 19:00:49 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate: Fix the name of the sintel blacklisting + +2014-05-02 18:50:41 +0200 Thibault Saunier + + * validate/data/Makefile.am: + * validate/data/disable_subtitle_track_while_paused.scenario: + validate: Add a scenario to disable subtitle track while paused + +2014-05-02 14:06:18 +0200 Thibault Saunier + + * validate/gst/validate/media-descriptor-writer.c: + * validate/gst/validate/media-descriptor-writer.h: + * validate/gst/validate/media-descriptor.h: + * validate/tools/gst-validate-media-check.c: + validate: Implement frame by frame writing in the media descriptor writer + + Add an option to fully parse media files in the gst-validate-media-check tool + +2014-05-01 14:58:14 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Execute position right when the pipeline reaches PAUSED + We might go to PAUSED SYNC if nothing happens in the pipeline + +2014-05-01 14:11:24 +0200 Thibault Saunier + + * validate/tools/gst-validate.c: + * validate/tools/launcher/apps/gst-validate.py: + validate: Add a scenario that disable subtitles + + Clean the sythax to define switch-track action that actually + desactivate the track + +2014-05-01 12:52:09 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Do not execute action when buffering + While buffering we should no try to execute anything as we would not be + controlling properly the execution. + + Activate scrub forward seeking for HTTP streams + +2014-05-01 12:34:35 +0200 Thibault Saunier + + * validate/data/Makefile.am: + * validate/data/switch_set_external_subtitle.scenario: + * validate/data/switch_subtitle_track_while_paused.scenario: + * validate/tools/launcher/apps/gst-validate.py: + validate: Add a scenario that switches subtitle track while paused + + Integrate it in the launcher + +2014-05-01 11:32:42 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate:launcher: Sensibly simplify scenario handling + +2014-05-01 10:27:53 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Plug a minor leak + +2014-04-30 15:51:43 +0200 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + validate: Use ges-launch recursing path new feature + And fix path to URI conversion + +2014-04-30 15:40:10 +0200 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/main.py: + * validate/tools/launcher/utils.py: + validate: Add a gst-validate-launcher documentation + +2014-04-30 11:52:00 +0200 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/main.py: + validate:launcher: Port OptionParser to ArgParse + +2014-04-30 11:20:43 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/main.py: + validate: Can not do reverse playback on sintel sample + + Minor improvement in the CLI + +2014-04-30 11:13:51 +0200 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + validate:launcher: Do not except meaningless argument in ges-launch + +2014-04-30 11:06:09 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + validate: Handle per file special scenarios + When a file is int the same folder as a media file and has a name like: + mediafilename.mkv.scenarios_name.scenario we run that scenario on that + particular file + +2014-04-30 09:35:03 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: Allow specifying scenarios to parse when lisiting them + It used to only handle the scenario present in proper paths, we + also need to handle special scenarios provided by users on the fly + +2014-04-29 20:00:21 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: Handle ERROR on the bus when monitoring the pipeline + This way the user get a clear information in the report about the issue + + sensibly cleanup code + +2014-04-29 19:04:46 +0200 Thibault Saunier + + * validate/data/Makefile.am: + * validate/data/switch_subtitle_track.scenario: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + validate: Add a scenarios that switchs subtitle track + + Make it easier and cleaner to tell that a switch is actually disabling + a track type. + And run the scenario in gst-validate-launcher by default + +2014-04-29 18:51:54 +0200 Thibault Saunier + + * validate/data/Makefile.am: + * validate/data/switch_audio_track_while_paused.scenario: + * validate/tools/launcher/apps/gst-validate.py: + validate: Add a switch_audio_track_while_paused scenario + And run it as a default + +2014-05-01 18:20:25 +0200 Thibault Saunier + + * validate/tools/gst-validate.c: + validate: Add an action to set an external URI file on playbin at runtime + +2014-05-01 18:19:50 +0200 Thibault Saunier + + * validate/tools/gst-validate.c: + validate: Override switch_track action when using a playbin + And use the playbin feature for that when the pipeline is based on playbin + +2014-05-01 18:17:44 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + validate: Do not g_strrstr with a NULL pointer as needle + +2014-05-01 18:16:16 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Print more details when executing the switch_track action + + Fix some issue in the memory freeing codepath of GstValidateAction + +2014-04-29 17:16:50 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + validate: Do not try to use a NULL iter + +2013-10-01 21:11:35 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + pad-monitor: check that no buffers are pushed after a pad is EOS + Make sure no resources are wasted after elements are done with the + current segment + +2014-04-28 13:08:09 +0200 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/main.py: + * validate/tools/launcher/utils.py: + launcher: Now using git annex to handle media files + +2014-04-26 09:52:37 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate: Expose a seeking method so other actions types can seek + Other action types might need to seek and we GstValidateScenario need + to know about it, add a method others can use to do the seeking + +2014-04-26 09:16:26 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/reporters.py: + Revert "validate:launcher: Always put gst-validate result as stderr in reports" + This reverts commit 925ff7542b69bb5516b6eb5b4488da23124a0cbc. + Actually jenkins never truncates on failure stacktrace... we do not + want to set gst-validate as failure stacktrace in our results. That + commit was not usefull. + +2014-04-26 08:11:20 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/reporters.py: + validate:launcher: Always put gst-validate result as stderr in reports + This way jenkins will always keep the information in its database even + if the test passes + +2014-04-25 18:33:33 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Pass -scenario.c into gst-indent and fix some docs + +2014-04-25 18:27:30 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Add a "dot-pipeline" action + +2014-04-25 18:26:50 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/gst-validate-utils.h: + validate: Return a boolean when parsing an enum string + +2014-04-25 13:42:03 +0200 Thibault Saunier + + * validate/data/adaptive_video_framerate.scenario: + * validate/data/adaptive_video_framerate_size.scenario: + * validate/data/adaptive_video_size.scenario: + * validate/data/force_key_unit.scenario: + * validate/data/pause_resume.scenario: + * validate/data/play_15s.scenario: + * validate/data/scrub_forward_seeking.scenario: + * validate/data/seek_backward.scenario: + * validate/data/seek_forward.scenario: + * validate/data/switch_audio_track.scenario: + validate:scenarios: Prefer stop action instead of EOS when appropriate + +2014-04-25 13:19:19 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + validate:launcher: Properly set error message when sending EOS did not work + +2014-04-25 13:18:41 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/main.py: + validate:launcher: Add a way to specify tests filtering only on defaults + +2014-04-25 13:17:39 +0200 Thibault Saunier + + * validate/data/switch_audio_track.scenario: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + validate:launcher: Add support for audio track switching scenario + +2014-04-25 11:32:04 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + validate:launcher: Make it possible to run any scenario test in gst-validate + +2014-04-25 11:31:27 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate:launcher: Do not run reverse playback on mpegts files + +2014-04-25 11:31:01 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate:launcher: Cleanup media descriptor usage + +2014-04-25 10:23:21 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/main.py: + validate:launch: Port to the new media_info format + +2014-04-24 15:41:50 +0200 Thibault Saunier + + * validate/gst/validate/Makefile.am: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/media-descriptor-parser.c: + * validate/gst/validate/media-descriptor-parser.h: + * validate/gst/validate/media-descriptor-writer.c: + * validate/gst/validate/media-descriptor-writer.h: + * validate/gst/validate/media-descriptor.c: + * validate/gst/validate/media-descriptor.h: + * validate/tools/gst-validate-media-check.c: + validate: Add a media-descriptor parser and writer + +2014-04-23 13:25:44 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-scenario.c: + validate: Minor printing cleanup + +2014-04-23 13:24:23 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-scenario.c: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: Add a 'stop' action to stop a pipeline + It uses the GST_MESSAGE_REQUEST state with the scenario as a source + so that application can stop running when they receive it on the bus. + +2014-04-23 11:47:10 +0200 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/reporters.py: + * validate/tools/launcher/utils.py: + validate:launcher: Use the new validatelog file + Making the output cleaner and clearer in junit XML file + +2014-04-23 11:27:41 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-bin-monitor.h: + * validate/gst/validate/gst-validate-report.c: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: Handle position printing at the monitor level + Instead of replicating that code all around + +2014-04-23 11:16:29 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/gst/validate/validate.c: + * validate/tools/gst-validate-transcoding.c: + validate: Add printing utilities + Allowing the user to print everyting in a file through the + GST_VALIDATE_FILE env variable + +2014-04-22 16:50:08 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Add an action to wait for a given amout of time + During that time we will just not execute any new action + + Lower WARNING to DEBUG when no playbcak_time is provided for an + action, it should just be 0. + +2014-04-22 12:02:35 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.h: + validate: Check that for raw, buffers are strictly contained in segment + For encoded data we might need buffers that have timestamp < + segment.start to make sure that we have the keyframe, etc... but for raw + data, buffer end should strictly be inside the segment, be more strict + about that. + +2014-04-22 11:21:34 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + validate: PAR is not a mandatory field + Also make it possible to check other not mandatory fields in the future + +2014-04-22 11:10:01 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + validate: Do not use GST_PTR_FORMAT when reporting + It will not work now that we have our own implementation of printf for that in Gst and + thus provide us with pretty useless infos + +2014-04-22 10:49:10 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + validate:launcher: Send SIGINT signal instead of killing the subprocess + This way we get the result from GstValidate even on timeouts + +2014-04-22 09:42:57 +0200 Thibault Saunier + + * validate/tools/gst-validate.c: + * validate/tools/launcher/apps/gst-validate.py: + validate:launcher: Always set sync=True on fakesink on playback pipelines + This way we are in closer condition of real sink playback. + + some minor cleanup in gst-validate.c + +2014-04-17 12:58:48 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate:launcher: ring-buffer-max-size is in bytes + +2014-04-17 12:17:03 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-reporter.h: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: Handle g_log errors at the gst-validate level + +2014-04-17 11:23:23 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Allow comments in scenario files + Comment are per line only and start with # + +2014-04-15 15:26:36 +0200 Thibault Saunier + + * validate/tools/launcher/main.py: + validate:launcher: Fix default blacklist management + +2014-04-02 19:14:30 +0200 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + validate:launcher: Print the number of the test being run + +2014-04-02 19:13:50 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-media-info.c: + validate: Avoid segfault when discovering fails + In that case the x->stream_info might not be set + +2014-04-02 12:12:11 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate:launcher: Fix mixup in media_check tests expected file path + +2014-03-31 13:54:27 +0200 Thibault Saunier + + * validate/tools/launcher/utils.py: + validate:launcher: Flush stdout each time we print + So everything gets printed on time on windows and jenkins + +2014-03-31 11:03:48 +0200 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate: launcher: Use the ConfigPraser object everywhere for file_infos + +2014-03-28 15:01:12 +0100 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + validate:launcher: Properly handle missing scenarios on the system + +2014-03-28 15:00:45 +0100 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + validate:launcher: Handle windows path to construct arguments + +2014-03-28 15:00:01 +0100 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/utils.py: + validate:launcher: Handle the fact that win32 apps end with .exe + +2014-03-28 11:30:01 +0100 Thibault Saunier + + * validate/configure.ac: + * validate/gst/validate/Makefile.am: + validate: Do not build LD_PRELOAD related code on windows + And do not forget to link against gst-pbutils + +2014-03-28 10:30:21 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-override-registry.c: + validate: Use GModule to 'dlopen' ovverrides + We want gst-validate to be cross platform so use cross platform tools + +2014-03-26 20:09:12 +0100 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/utils.py: + validate:launcher: Put gst logs in a specific file + + Make default timeout 30seconds just in case. + +2014-03-26 19:37:44 +0100 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/main.py: + validate: launcher: Let the use debug on test fail + When a test timeouts, let the user know about the subprocess etc, + and let him possibly connect gdb to it. + +2014-03-26 11:46:48 +0100 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + validate:launcher: Do not set sample path to letter in ges-launch + +2014-03-26 11:00:32 +0100 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate:launcher: Start the server only when actually needed to run filtered tests + +2014-03-26 10:56:58 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + validate: Do not query pad caps to check if caps are properly fowarded + Query caps will actually get the caps from downstream and those caps + might be different in case there is a Filter in between. What we want is + to check that the caps set on the internally linked pads are correct. + +2014-03-19 18:42:37 +0100 Thibault Saunier + + * validate/tools/launcher/main.py: + launcher: Allow user to set media-files directory + That was broken by 71dee6c3843d02d9d41bbb353cb3fa653190018d + +2014-03-19 17:43:43 +0100 Thibault Saunier + + * validate/tools/gst-validate.c: + tools:validate: Start printing position on ASYNC_DONE + As this is what is done in the scenarios. + +2014-03-19 18:09:09 +0100 Edward Hervey + + * validate/tools/launcher/main.py: + launcher: Don't hardcode option defaults + Since they are relative to other options, we need to post-process them + to get the proper value. + Fixes using the launcher with non-default MAIN_DIR + +2014-03-19 17:13:14 +0100 Edward Hervey + + * validate/tools/launcher/main.py: + launcher: Warn if MAIN_DIR isn't present + And move blacklist file listing to further down + +2014-03-19 17:04:14 +0100 Edward Hervey + + * validate/tools/launcher/main.py: + launcher: No need to start a web server when listing tests + It's not needed and makes listing faster. + Also sort the list of tests + +2014-03-19 17:03:05 +0100 Edward Hervey + + * validate/tools/launcher/main.py: + launcher: --sync: Only update/clone git repo if specified + Allows: + * handling non-git-based asset directory + * working offline + * working without forcing updates + +2014-03-19 17:02:03 +0100 Edward Hervey + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: Handle non-set env variable + Nothing guarantees it's present/set + +2014-03-12 15:23:33 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Do not be strict about position after not accurate seek + +2014-03-12 14:24:02 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Cleanup output and pass into gst-indent + +2014-03-12 12:21:38 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Cleanup output of --list-scenarios + +2014-03-12 12:04:52 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: scenario: Load scenario if the name is actually a path to a file + +2014-02-12 11:20:06 +0100 Thibault Saunier + + * validate/tools/launcher/main.py: + validate: tools: Fix path to media folder + +2014-02-19 13:07:03 +0100 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + validate:tools: Clean test between runs when running forever + +2014-02-19 10:31:15 +0100 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + validate: Do not check result furthers if alredy set as passing + +2014-02-19 09:58:22 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Pass into gst-indent + +2014-02-19 09:56:12 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Add actions to the actions list only when they are fully parsed + Otherwize in some corner cases they can be executed before they are actually parsed + +2014-01-24 17:36:53 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Ignore EOS actions that can not be executed + +2014-02-18 18:49:00 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate: Handle various paths in GST_VALIDATE_SCENARIOS_PATH + +2014-02-18 18:15:33 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario: Make GstValidateAction a GstMiniObject + +2014-02-18 18:13:39 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/gst-validate-utils.h: + validate: Move enums and flags deserialization from scenario to utilities + This way it can be reused. + +2014-02-18 18:09:37 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + validate:scenario: Make the pipeline puiblic + This way people can access it from outside the main action implementation. + +2014-02-14 16:07:51 +0100 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + validate:launcher: Avoid running useless tests + For example we should not check if duration are equal when transcoding + with scenario set. + Also checking if position is in the seeked segment should be done at + a lower level + +2014-02-13 15:35:01 +0100 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + validate:launcher: Avoid seeking in output files to parse them + Tihs creates issue and missing content. + +2014-02-13 15:34:10 +0100 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate:launcher: Properly classify test for media check + +2014-02-13 15:33:25 +0100 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/utils.py: + validate:launcher: Handle issue with unknown framerate in HLS while transcoding + +2014-02-13 15:31:58 +0100 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/utils.py: + validate:tools: Handle cases were EOS does not stop the pipeline in the launcher + + Fix parsing of GstClockTime + + Avoid using play_15s scenario when not necessary + +2014-02-12 11:18:14 +0100 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/main.py: + * validate/tools/launcher/utils.py: + validate: tools: Use the new scenario discovering fearure in the launcher + +2014-02-12 00:28:41 +0100 Thibault Saunier + + * validate/data/adaptive_video_framerate.scenario: + * validate/data/adaptive_video_framerate_size.scenario: + * validate/data/adaptive_video_size.scenario: + * validate/data/alternate_fast_backward_forward.scenario: + * validate/data/fast_backward.scenario: + * validate/data/fast_forward.scenario: + * validate/data/force_key_unit.scenario: + * validate/data/pause_resume.scenario: + * validate/data/play_15s.scenario: + * validate/data/reverse_playback.scenario: + * validate/data/scrub_forward_seeking.scenario: + * validate/data/seek_backward.scenario: + * validate/data/seek_forward.scenario: + * validate/data/seek_forward_backward.scenario: + * validate/data/seek_with_stop.scenario: + * validate/data/simple_seeks.scenario: + * validate/data/switch_audio_track.scenario: + * validate/data/update_start.scenario: + * validate/data/update_stop.scenario: + validate: Update all scenario to use the new description feature + + Fix minor issues in scenario files + +2014-02-12 00:28:18 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: Add a way to save details about avalaible scenarios in a file + +2014-02-11 23:05:00 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/tools/gst-validate.c: + validate: Add a way to add a "description" to scenario files + Print details about the descriptions when listing scenario in a KeyFile + format + The description can contain any information about the scenario such as its duration before + EOS, how long the pipeline needs to be so the scenario can be applied...etc + +2014-02-11 23:09:57 +0100 Thibault Saunier + + * validate/tools/gst-validate-transcoding.c: + validate: tools: Init gst-validate before listing scenarios + And return 0 when only listing scenarios + +2014-02-10 16:48:44 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + validate:scenario: Handle backslashes in scenario files + +2014-02-06 17:24:30 +0100 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/main.py: + * validate/tools/launcher/utils.py: + validate:tools: Rework the way we handle options + Make groups so it is easier for users to find what they look for + By default have 1 single directory where everything is oututed + (main-dir) + Add a way to specify how and where to look for remote assets + +2014-02-06 17:23:10 +0100 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + validate:tools:launcher: Take into account the position value when rendering + When rendering a files we try to use the size of the outputed file to + determine wether we are timeout or not, but if that fails + try to check the position + +2014-02-06 17:22:36 +0100 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/apps/gst-validate.py: + validate: Better organize rendered files + +2014-01-31 12:21:21 +0100 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/utils.py: + validate:tools: Use regex for parsing when appropriate + +2014-01-31 00:23:29 +0100 Thibault Saunier + + * validate/tools/launcher/main.py: + validate:toold: Add a --output-dir parametter + +2014-01-31 00:22:57 +0100 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/apps/gst-validate.py: + validate:tools: Keep file extension in test classnames + + add test "namespace" in transcoded files + +2014-01-30 16:59:21 +0100 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate:tools: Add a class to back pipeline creation in gst-validate + +2014-01-30 16:58:58 +0100 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/utils.py: + validate:tools: Define supported protocols in an enum + +2014-01-30 16:56:51 +0100 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + validate: toold: Properly define scenario properties + +2014-01-30 16:38:37 +0100 Thibault Saunier + + * validate/data/Makefile.am: + * validate/data/reverse_playback.scenario: + * validate/data/simple_backward.scenario: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + validate: Rename simple_backward to reverse_playback as this is what it does + +2014-01-30 15:40:21 +0100 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/apps/gst-validate.py: + validate:tools: Add namespace in apps global variables + Avoiding conflicts + +2014-01-30 13:36:04 +0100 Thibault Saunier + + * validate/tools/launcher/main.py: + validate:tools: Allow user to append paths to medias + +2014-01-30 13:25:57 +0100 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + validate:tools: Make use of the new seek_with_stop scenario + +2014-01-30 12:42:25 +0100 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/utils.py: + validate:tools: Implement the logic of validate ouput parsing in the baseclass + + Add some logic to check that we are mot playing outside wanted segment + +2014-01-30 12:20:33 +0100 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/main.py: + tools:validate: Make default blacklist handled by managers themselves + +2014-01-30 11:59:54 +0100 Thibault Saunier + + * validate/data/Makefile.am: + * validate/data/scrub_forward_seeking.scenario: + * validate/data/seek_backward.scenario: + * validate/data/seek_forward.scenario: + * validate/data/seek_with_stop.scenario: + validate: data: Avoid using seek.stop time when not necessary + Instead send an EOS. + And add a seek_with_stop scenario to test that particular feature + +2014-01-29 17:39:14 +0100 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate:tools: Only discover files with media-check + +2014-01-29 17:37:57 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-media-info.c: + * validate/gst/validate/gst-validate-media-info.h: + * validate/tools/gst-validate-media-check.c: + validate:tools: Add a 'discover-only' option to media-check + +2014-01-27 12:20:02 +0100 Thibault Saunier + + * validate/data/seek_forward.scenario: + data: Let playback until the end on last seek of seek_forward if duration < 30s + +2014-01-24 16:38:12 +0100 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/main.py: + validate: tools: Add a --fatal-error option to the launcher + +2014-01-24 13:59:56 +0100 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + validate: tools: Implement the notion of hard timeout + Allowing to define timeout that is not relative to the last observed number. + +2014-01-24 11:41:25 +0100 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + validate: tools: Create a class for scenarios + +2014-01-24 11:31:42 +0100 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate: tools: Change timeouts depending on used protocol + +2014-01-24 11:29:50 +0100 Thibault Saunier + + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + * validate/tools/launcher/utils.py: + validate:tools: Implement Buffering support in the various tools + +2014-01-23 00:15:54 +0100 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/main.py: + validate:tools: Blacklist some scenario/protocol combinations + And add the option for user to easilly blacklist tests + +2014-01-22 23:25:09 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-reporter.c: + validate: Plug a leak in validate-reporter + +2014-01-22 23:22:59 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-reporter.c: + validate:tools: Do not forget to give a ref for reporter's reports + Also enhance a bit report 'wording' + +2014-01-15 16:11:39 +0100 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/main.py: + * validate/tools/launcher/utils.py: + validate:tools: Print test result in the terminal after the end of each test + +2014-01-15 16:07:26 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Enhance explanation about seek execution failure + +2014-01-14 18:07:46 +0100 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/main.py: + validate:tools: Add an option to run testforever + +2014-01-14 18:05:45 +0100 Thibault Saunier + + * validate/tools/launcher/utils.py: + validate: tools: Cleanup the way we return code in position query + +2014-01-14 10:32:53 +0100 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + validate:tools: Use the same semantic for all tests classnames + +2014-01-14 10:31:27 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Do not remove GSource if it has never been set + +2014-01-14 10:28:01 +0100 Thibault Saunier + + * validate/data/play_15s.scenario: + data: Add a scenario where we send EOS after 15secs if the duration is > to that + +2014-01-13 17:31:57 +0100 Thibault Saunier + + * validate/tools/launcher/Makefile.am: + * validate/tools/launcher/RangeHTTPServer.py: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/httpserver.py: + * validate/tools/launcher/main.py: + validate:tools: Add support for testing http streams locally + +2014-01-13 09:47:45 +0100 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate:tools: Some cleanup in gst-validate test launcher + +2014-01-13 11:13:02 +0100 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate:tools: Add actuall tests for media checking + +2014-01-13 11:07:43 +0100 Thibault Saunier + + * validate/tools/gst-validate-media-check.c: + validate: tools: media-check: When comparing with a file just compare + We do not want to know if the file is seekable etc, but in that case we + want to see that the results are stable throughout the various runs + Also make sure to report an understandable error if the media file info + could not be parsed + +2014-01-13 09:32:14 +0100 Thibault Saunier + + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate: tools: Do not dot the pipeline every 50ms, it is a bit exessive + +2014-01-10 18:00:27 +0100 Thibault Saunier + + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + validate:tools: Return an exit code != 0 if pipeline can't go to playing + And give some information to the user about why the return code is !=0 + everywhere it happens + +2014-01-10 17:21:44 +0100 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + validate:tools: Add informations about the test in the log files + +2014-01-10 16:56:44 +0100 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + validate:tools: Remove reference to get_backtrace which is not implemented + + Enhance Message about launched apps + +2014-01-10 16:46:00 +0100 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/reporters.py: + validate:tools: Do not duplicated name in the classname in xunit reports + +2014-01-10 15:31:01 +0100 Thibault Saunier + + * validate/tools/launcher/utils.py: + validate:tools: Do not check if position > duration + This is actually done by the scenario themselve. Instead if it is the + case, we return 0, this way it will timeout if it happens too many times + concecutively + +2014-01-10 15:30:38 +0100 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/reporters.py: + * validate/tools/launcher/utils.py: + validate:tools: Properly inform the user about the log location when test fails + +2014-01-10 15:29:31 +0100 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/main.py: + validate:tools: Add an option to generate .media_info files + So we can properly choose what media should be tested only placing + media_file as needed. + +2014-01-10 15:27:46 +0100 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/apps/gst-validate.py: + validate:tools: use more scenarios in gst-validate launcher + And ensure that the list does not get mixed up with as we are sharing + "symboles" between all the files + +2014-01-10 15:26:29 +0100 Thibault Saunier + + * validate/data/simple_backward.scenario: + validate:tools: Play the entire file in simple_backward + +2014-01-10 14:31:24 +0100 Thibault Saunier + + * validate/data/fast_forward.scenario: + validate:tools: Fix the fast forward scenario to handle any file duration + +2014-01-10 12:41:30 +0100 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate:tools: Do not try to transcode images + +2014-01-10 12:01:43 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-media-info.c: + * validate/gst/validate/gst-validate-media-info.h: + validate: Properly handle images in the media-info helper + In the case of images we should not check reverse playback, fast + forward etc... + We also should keep the information + +2014-01-10 11:36:10 +0100 Thibault Saunier + + * validate/configure.ac: + * validate/tools/launcher/loggable.py: + * validate/tools/launcher/main.py: + validate:tools: Minor cleanups + +2014-01-10 11:35:47 +0100 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate:tools: Fix classname in gst-launch transcoding tests + +2014-01-10 11:11:10 +0100 Thibault Saunier + + * validate/tools/launcher/reporters.py: + validate:tools: Properly name the project launcher in the report + +2014-01-10 10:58:54 +0100 Thibault Saunier + + * validate/tools/launcher/main.py: + validate:tools: Create the rendering directory if it does not exist + +2014-01-10 10:27:25 +0100 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/main.py: + * validate/tools/launcher/utils.py: + validate:tools: Add an option to desativate ANSI colors + And enhance some debugging output + +2014-01-10 10:12:13 +0100 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/utils.py: + validate:tools: Remove our dependency to PyGobject + +2014-01-09 18:43:15 +0100 Thibault Saunier + + * validate/tools/launcher/Makefile.am: + * validate/tools/launcher/apps/Makefile.am: + * validate/tools/launcher/main.py: + validate:tools: Do not forget to add Makefile.am and main.py + +2014-01-09 16:57:54 +0100 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/baseclasses.py: + validate:tools: Enhance the way we detect if ges-launch can be used + We make sure it has been compiled against gst-validate + +2014-01-09 15:24:52 +0100 Thibault Saunier + + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/utils.py: + validate:tools: Set video/webm instead of video/x-matroska as caps for webm + + some mirore indentation cleanups + +2014-01-09 15:24:05 +0100 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate:tools: Do not forget to keep our ref to file_info g-v-transcode + +2014-01-09 15:23:38 +0100 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/utils.py: + validate:tools: Cleanup how we check result of rendering test + Factor out a method in the utils, and make use of it for both ges-launch and + gst-validate-transcode + +2014-01-09 15:20:46 +0100 Thibault Saunier + + * validate/tools/launcher/apps/gst-validate.py: + validate:tools: Don't give file duration as timeout for gst-validate + We use the other mean letting us actually control the process + advancement. + +2014-01-09 15:17:53 +0100 Thibault Saunier + + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/utils.py: + validate:tools: Veryfy test manager are operationnal before using them + +2014-01-09 15:15:51 +0100 Thibault Saunier + + * validate/tools/gst-validate-launcher.in: + * validate/tools/launcher/apps/ges-launch.py: + validate:tools: Move the main function in a dedictaed file + +2014-01-09 11:14:19 +0100 Thibault Saunier + + * validate/tools/launcher/reporters.py: + * validate/tools/launcher/utils.py: + validate:tools: Print some statistic at the end of the test run + +2014-01-09 11:13:40 +0100 Thibault Saunier + + * validate/tools/gst-validate.c: + validate:tools: Print position every 50ms in gst-validate + +2014-01-09 09:39:05 +0100 Thibault Saunier + + * validate/configure.ac: + * validate/tools/Makefile.am: + * validate/tools/gst-validate-launcher.in: + * validate/tools/launcher/__init__.py: + * validate/tools/launcher/apps/ges-launch.py: + * validate/tools/launcher/apps/gst-validate.py: + * validate/tools/launcher/baseclasses.py: + * validate/tools/launcher/loggable.py: + * validate/tools/launcher/reporters.py: + * validate/tools/launcher/utils.py: + validate:tools: Rename files around and integrate into autotools + File distribution used to be messy, clean it all up. Also make sure the + launcher is integrated into the autotools. + +2014-01-09 09:28:02 +0100 Thibault Saunier + + * validate/tools/testdefinitions.py: + validate: tools: Enhance error message for GstValidate tests + +2014-01-09 09:27:50 +0100 Thibault Saunier + + * validate/tools/testdefinitions.py: + * validate/tools/utils.py: + validate: tools: Concider timeouts as errors when printing tests + +2014-01-09 09:14:27 +0100 Thibault Saunier + + * validate/tools/apps/ges-projects-tests.py: + * validate/tools/apps/gst-validate.py: + * validate/tools/gst-validate-launcher.py: + * validate/tools/testdefinitions.py: + * validate/tools/utils.py: + validate: tools: Refactor and add a GstValidateTranscodeTest class + +2014-01-08 18:51:14 +0100 Thibault Saunier + + * validate/tools/apps/gst-validate.py: + * validate/tools/gst-validate-launcher.py: + * validate/tools/loggable.py: + * validate/tools/reporters.py: + * validate/tools/testdefinitions.py: + validate: launcher: add the debug logger from pitivi + It is way more powerfull, simple to use and usefull + than the stock python one and has been proved to work reliably + +2014-01-13 09:41:16 +0100 Thibault Saunier + + * validate/tools/gst-validate.c: + validate: tools: Unref the pipeline before the runner and monitor + Avoids segfault in some cases, and monitors and runners have week ref on + their targets. + +2014-01-08 09:49:38 +0100 Thibault Saunier + + * validate/tools/apps/gst-validate.py: + validate: tools: Add a gst-validate test manager + +2014-01-08 09:44:02 +0100 Thibault Saunier + + * validate/gst/validate/gst-validate-media-info.c: + validate: tools: media-info: Fixes in the media file descriptor parsing code + We used to always fail when the user was passing something not NULL as err + +2013-12-31 11:45:07 +0100 Thibault Saunier + + * validate/tools/apps/ges-projects-tests.py: + * validate/tools/gst-validate-launcher.py: + * validate/tools/reporters.py: + * validate/tools/testdefinitions.py: + * validate/tools/utils.py: + validate: tools: Cleanup test launcher tool + Previous commit was not meant to be pushed and those two should have + been fixed up together, sorry for the mistake + +2014-01-30 15:52:34 -0300 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/Makefile.am: + validate: fix parallel build + Without this, parallel building with > 2 jobs fails. + Also, LDFLAGS should not contain -l flags but _LIBADD. + +2014-01-30 15:47:15 -0300 Reynaldo H. Verdejo Pinochet + + * validate/gst/validate/gst-validate-default-overrides.c: + validate: drop unneeded stdio include + +2013-12-31 11:45:07 +0100 Thibault Saunier + + * validate/tools/apps/ges-projects-tests.py: + * validate/tools/gst-validate-launcher.py: + * validate/tools/reporters.py: + * validate/tools/testdefinitions.py: + * validate/tools/utils.py: + Add a test launcher tool + +2013-11-25 21:51:11 +0100 Lubosz Sarnecki + + * validate/gst/validate/Makefile.am: + * validate/pkgconfig/gst-validate.pc.in: + validate: fix installation + * install headers + * fix libname in pk file + +2013-11-15 05:22:24 -0500 Vincent Penquerc'h + + * validate/gst/validate/gst-validate-scenario.c: + validate-scenarios: list scenarios in GST_VALIDATE_SCENARIOS_PATH + GST_VALIDATE_SCENARIOS_PATH was used only for loading scenarios, + so any in that path would not be listed by -l. + Change-Id: If3cb94867ef3876933bda02477675c8ccf67baaf + +2013-10-18 16:22:03 -0300 Thibault Saunier + + * validate/tools/gst-validate-transcoding.c: + tools: transcoding: Avoid reencoding unless explicitely specified + +2013-10-28 19:49:52 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: Do not concider TIME_NONE as 0 for serialized events + In case we have serialized events right after a buffer that had no + timestamp set we concider that last timestamp was 0, but we can + actually not concider the timestamp at all in that case as it is + only "meaningless value". + +2013-10-19 13:41:01 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + tools: Simplify the setting of action scenario vs config scenario + This make it easier for user to understand the difference between + the two concepts and avoids confusion. + Change-Id: Ib42913722c93a1e7e3c8b156173c458230946592 + Conflicts: + validate/tools/gst-validate-transcoding.c + validate/tools/gst-validate.c + +2013-10-25 11:33:54 +0200 Thibault Saunier + + * validate/tools/gst-validate.c: + scenario: Do not execute anything when listing scenarios + +2013-10-25 11:31:58 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Add a "set-feature-rank" config action + This action can be used to change the rank of a particular element, + so you can force a particular element to be used when using + autoplugging elements (such as decodebin, encodebin, and friends) + +2013-10-25 11:29:04 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/tools/gst-validate-transcoding.c: + scenario: Add support for "config" actions, actions executed at parse time + This type of actions is used to change some parametter on GStreamer + core and it plugins, it can be fore example, to change the rank of a + plugin or things like that. + +2013-10-16 17:35:36 -0300 Thibault Saunier + + * validate/tools/gst-validate-transcoding.c: + validate-transcoding: Dot pipeline on error + +2013-10-26 03:01:37 -0700 Zaheer Abbas Merali + + * vagrant/Vagrantfile: + * vagrant/ansible_hosts: + * vagrant/gst-streaming-server-git.yml: + * vagrant/gstreamer-git.yml: + * vagrant/gstreamer.yml: + * vagrant/ipython.yml: + * vagrant/playbook.yml: + vagrant: initial commit + Vagrant environment to do GStreamer development, debugging and + testing. + +2013-10-21 13:06:46 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: handle streams with unknown duration + +2013-10-21 09:08:18 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: start handling missing plugin messages + We only print them to the debug log for now. + +2013-10-21 09:07:09 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + * mediainfo/src/mi-preview.vala: + mi-preview: use ensure_native() in realized() + This fixes X crashers at startup when preparing the overlay. + +2013-10-19 21:15:08 +0200 Stefan Sauer + + * mediainfo/TODO: + mi/TODO: planning update + +2013-10-19 21:13:46 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: go back to use the sync api + If we discover 'too quickly' the machinery seems to get into a state, where it + does not discover anything anymore. + +2013-10-18 23:33:50 +0200 Stefan Sauer + + * mediainfo/src/mi-app.vala: + * mediainfo/src/mi-info.vala: + * mediainfo/src/mi-preview.vala: + mi-preview: reflow the overlay sync + We need to listen to preview-widget resizing to send an expose to the gst- + overlay. Defer discovering until the ui has be realized. + +2013-10-18 18:22:33 +0200 Stefan Sauer + + * mediainfo/src/mi-app.vala: + mi-app: use an idle-handler to set the initial directory + This ensures we don't emit selection changed signals before we're up and running. + +2013-10-17 22:34:25 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: improve media preview + Set double_buffering when we analyzed the media. Drop signal handlers on preview + widget for delayed configuration. Prepare preview as soon as we have discovered. + +2013-10-14 11:25:39 -0300 Thibault Saunier + + * validate/configure.ac: + * validate/tools/Makefile.am: + Properly link against gstreamer-video as it is now needed + +2013-10-14 11:20:03 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-media-info.c: + media-info: Do not use GST_PTR_FORMAT with g_print + Fix compilation + +2013-10-14 11:07:03 -0300 Thibault Saunier + + * validate/data/scrub_forward_seeking.scenario: + data: Avoid races in the scrub_forward seeking scenario + Make sure that it does not last too long if the file is long (scrubing + on 10 secs maximum), and make sure that we do not end up seeking after + the max duration + +2013-10-14 11:05:48 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + report: Set refcount=1 when creating a report + As it should start with 1 reference, not 0 + +2013-10-09 09:35:29 -0300 Thibault Saunier + + * validate/data/Makefile.am: + * validate/data/force_key_unit.scenario: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/tools/gst-validate-transcoding.c: + scenario: Add an action that checks the "force-key-unit" event execution + +2013-10-09 09:33:06 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + scenario: Make the get_clocktime helper a public method + So it can be reused outside of the core code + +2013-10-07 19:47:15 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: Do not try to compare 2 not fixed values + There is no reliable way of checking those values in the case they + are not fixed, let's just make sure we get fixed values before + executing the check + +2013-10-07 17:40:54 -0300 Thibault Saunier + + * validate/data/Makefile.am: + * validate/data/adaptive_video_framerate_size.scenario: + data: Add an adaptive video framerate and size scenario + +2013-10-07 17:18:37 -0300 Thibault Saunier + + * validate/data/Makefile.am: + * validate/data/adaptive_video_framerate.scenario: + * validate/tools/gst-validate-transcoding.c: + data: Add an adaptive video framerate scenario + +2013-10-07 12:08:28 -0300 Thibault Saunier + + * validate/data/Makefile.am: + * validate/data/adaptive_video_size.scenario: + data: Add a scenario where we change the video size on during playback + +2013-10-07 12:06:22 -0300 Thibault Saunier + + * validate/tools/gst-validate-transcoding.c: + transcoding: Add a new action to change restriction caps at runtime + +2013-10-07 12:07:47 -0300 Thibault Saunier + + * validate/tools/gst-validate-transcoding.c: + transcoding: Fix the way we get pad caps + +2013-10-07 10:59:39 +0200 Stefan Sauer + + * mediainfo/TODO: + * mediainfo/src/mi-info.vala: + mi-todo: planning and todo comment update + +2013-10-07 10:59:15 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: add two more wikilinks + +2013-10-07 10:07:31 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: filter language-code from tags + We already show the language code as a separate field. + +2013-10-05 13:29:52 -0300 Thibault Saunier + + * validate/data/alternate_fast_backward_forward.scenario: + * validate/data/fast_backward.scenario: + * validate/data/fast_forward.scenario: + * validate/data/seek_backward.scenario: + * validate/data/seek_forward.scenario: + * validate/data/seek_forward_backward.scenario: + * validate/data/simple_backward.scenario: + * validate/data/simple_seeks.scenario: + * validate/data/update_start.scenario: + * validate/data/update_stop.scenario: + data: Set seeks to accurate+flush by default + +2013-10-05 12:44:39 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Pass through gst-indent + +2013-10-05 12:43:27 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Use g_error instead of exit (0) + +2013-10-05 12:43:03 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Factor out function to get GstClockTime out of a structure + +2013-10-05 12:01:46 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Use a weak ref to the pipeline + We are listening to it, we should not be owning a ref to it. + +2013-10-05 12:00:35 -0300 Thibault Saunier + + * validate/docs/validate-usage.txt: + * validate/gst/validate/gst-validate-runner.c: + * validate/tools/gst-validate-transcoding.c: + runner: Use "18" as exit code in case of error + It is a random number, but it will in most cases give people a hint + that gst-validate reported a critical issue, and thus set the return + code, only by looking at it + Also make use of gst_validate_runner_print() in + gst-validate-transcoding.c as we were copy pasting that method there. + +2013-10-03 19:23:57 -0300 Thibault Saunier + + * validate/data/seek_backward.scenario: + * validate/data/seek_forward.scenario: + * validate/gst/validate/gst-validate-scenario.c: + scenario: Handle formulas in playback_time + And port seek forward/backward scenarios to relative seeking + +2013-09-28 02:18:55 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-scenario.c: + scenario: Check that all action were properly executed + +2013-09-28 00:15:13 +0200 Thibault Saunier + + * validate/data/Makefile.am: + * validate/data/scrub_forward_seeking.scenario: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + scenario: Add the notion of repeated actions + +2013-09-28 00:05:51 +0200 Thibault Saunier + + * validate/gst/validate/Makefile.am: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-utils.c: + * validate/gst/validate/gst-validate-utils.h: + utils: Add util functions to parse simple mathematical expressions + And make use of it to set the start of a seek + +2013-09-28 00:12:07 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Error out and exit when we fail loading a scenario + +2013-10-04 09:58:17 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: add more wikilinks + +2013-10-04 07:51:46 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: pretty print framerates + Avoid to print 0 fps. Handle the special 0/1 case for still images. + +2013-10-03 18:14:18 -0400 Olivier Crête + + * validate/gst/validate/gst-validate-scenario.h: + gst-validate-scenario: Only typedef the struct once + Some gcc versions don't like the typedef being done twice + +2013-10-03 22:23:22 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: add a helper to format bit-rates + Print bit-rates in kbit/sec. Add handling for unknown values and ranges. + +2013-10-03 22:22:46 +0200 Stefan Sauer + + * mediainfo/src/mi-preview.vala: + mi-preview: ensure that natural-size >= min-size + +2013-10-01 08:21:45 +0200 Stefan Sauer + + * mediainfo/TODO: + mi/TODO: planning update + +2013-10-01 07:48:20 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: reset toc info when we did not get disco info + +2013-10-03 05:32:54 -0400 Vincent Penquerc'h + + * validate/gst/validate/gst-validate-scenario.c: + scenario: do not set default seek flags + Seeks will be done with no particular flags, unless specified + in the scenario. + +2013-09-30 15:39:54 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: filter buffer entries from caps + Filter buffer entries from caps before showing them as string. + +2013-09-28 07:19:59 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: show tooltip for caps labels with full caps string + +2013-09-28 07:12:27 +0200 Stefan Sauer + + * mediainfo/TODO: + * mediainfo/src/mi-info.vala: + mi-info: send seek events when clicking toc entries + Get the start-pos from the active toc entry and seek. + +2013-09-27 08:03:59 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: use a TreeView for the toc + Use a TreeView with a TreeStore to show toc-entries. + +2013-09-30 09:51:21 -0400 Olivier Crête + + * validate/tools/gst-validate.c: + gst-validate: Don't use the GOptionContext after freeing it + +2013-09-25 08:19:26 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: indent toc entries + Also add todo for how to make it a treeview instead + +2013-09-25 08:01:29 +0200 Stefan Sauer + + * mediainfo/TODO: + TODO: planning + +2013-09-25 07:58:49 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: mark nullable parameters as such + +2013-09-25 07:54:43 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: add start/stop times in toc + +2013-09-21 00:23:17 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: Check if channel-mask is present only if channels > 2 + As it is not a mandatory field otherwize + https://bugzilla.gnome.org/show_bug.cgi?id=708499 + +2013-09-19 07:38:20 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Add GST_VALIDATE_SCENARIOS_PATH environment variable + So you can specify the PATHS where to look for scenario files + +2013-09-16 10:03:07 -0300 Thiago Santos + + * validate/data/update_start.scenario: + * validate/data/update_stop.scenario: + scenarios: add 2 new scenarios for seeks with different seek types + They test seeks that only update the stop or the start position, some + demuxers seem not to handle the case where start type is set to None. + +2013-09-13 12:09:30 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-scenario.c: + validate-scenario: track position query results closer + Always keep probing the pipeline for the current position and compare + with the latest requested seek segment to detect if the seek boundaries + are being respected + +2013-09-17 15:56:19 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + scenario: Make it possible to define mandatory fields + And give a descrpition for actions + +2013-09-02 11:11:15 -0400 Vincent Penquerc'h + + * validate/data/Makefile.am: + * validate/data/switch_audio_track.scenario: + * validate/gst/validate/gst-validate-scenario.c: + scenario: add a track switch command, and an audio track switch test + The "switch-track" command can be used to switch tracks. The "type" + argument selects which track type to change (can be "audio", "video", + or "text"). The "index" argument selects which track of this type + to use: it can be either a number, which will be the Nth track of + the given type, or a number with a "+" or "-" prefix, which means + a relative change (eg, "+1" means "next track", "-1" means "previous + track"). + Conflicts: + validate/gst/validate/gst-validate-scenario.c + +2013-09-16 18:48:38 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Make it possible to register action parsing funcs before init + +2013-09-13 15:48:56 -0300 Thibault Saunier + + * validate/Makefile.am: + * validate/configure.ac: + * validate/pkgconfig/Makefile.am: + * validate/pkgconfig/gst-validate-uninstalled.pc.in: + * validate/pkgconfig/gst-validate.pc.in: + validate: Add .pc files so applications can link against us + +2013-09-15 15:11:53 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: extact helper to format times + +2013-09-13 08:16:34 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: comment updates + +2013-09-13 08:12:34 +0200 Stefan Sauer + + * mediainfo/TODO: + TODO: update planing + +2013-09-13 08:12:05 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: extract ui-helper for format/codec-rows + +2013-09-13 07:55:14 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: extract ui helper for adding an entry + A helper to add a label + str formatted details as a table row. + +2013-09-11 08:21:06 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: extract more common ui code into helpers + +2013-09-09 18:48:10 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: reshuffle container widgets + Prepare for handling nested containers. + +2013-09-09 18:37:24 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: code cleanups + First reset the info pane and then check/update. This fixes not resetting the + tabs on info==null. + +2013-09-09 18:36:47 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: fix type + It is 'Notebook' and not 'NoteBook'. + +2013-09-06 08:56:05 +0200 Stefan Sauer + + * mediainfo/src/mi-app.vala: + * mediainfo/src/mi-info.vala: + * mediainfo/src/mi.vala: + mi: code cleanups, comments + +2013-09-06 08:03:51 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: fix tab-index in compact mode + +2013-09-05 09:18:26 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: sort streams by stream_id + This way we are activating the right stream when switching tabs. + +2013-09-05 09:18:04 +0200 Stefan Sauer + + * mediainfo/TODO: + TODO: spelling fixes and update + +2013-09-04 09:17:28 +0200 Stefan Sauer + + * mediainfo/src/mi-app.vala: + * mediainfo/src/mi.vala: + app: allow giving an uri instead of a directory as a startup arg + This way we can play streams. + +2013-09-04 09:16:47 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + * mediainfo/src/mi-preview.vala: + preview: fix minimal size + We need some minial size, otherwise we can enlarge, but not shrink the window. + +2013-09-04 09:15:34 +0200 Stefan Sauer + + * mediainfo/TODO: + TODO: small ideas update + +2013-09-04 09:15:07 +0200 Stefan Sauer + + * mediainfo/HACKING: + * mediainfo/src/mi-info.vala: + HACKING: update instructions + +2013-09-03 22:03:19 +0200 Stefan Sauer + + * mediainfo/src/Makefile.am: + * mediainfo/src/mi-info.vala: + * mediainfo/src/mi-preview.vala: + preview: extract preview area as separate widget + +2013-09-03 07:41:46 +0200 Stefan Sauer + + * mediainfo/src/mi-app.vala: + * mediainfo/src/mi-info.vala: + * mediainfo/src/mi.vala: + mi: update my name and years + +2013-09-02 22:25:09 +0200 Stefan Sauer + + * mediainfo/HACKING: + * mediainfo/src/Makefile.am: + * mediainfo/vapi/config.vapi: + mi: set the log domain + +2013-09-02 09:52:30 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: comment updates + +2013-09-02 09:49:51 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: turn stdout.printf into debug log calls + +2013-09-02 09:41:24 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: add todo for stream switching + +2013-09-02 09:40:58 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: playbin handles force-aspect-ration in 1.0 + +2013-08-30 10:53:13 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: improve sizing + We're now hinting the scrolled window about the content size to avoid empty space + scrolling. + +2013-08-30 08:41:48 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: basic subtitle support + +2013-08-30 08:26:37 +0200 Stefan Sauer + + * mediainfo/src/mi-app.vala: + * mediainfo/src/mi-info.vala: + video-area: improve resizing of the video area + The browser pane does not expand by default. Track aspect-ration for the + currently displayed object. Use an aspect frame as a container for the + drawing-area. + +2013-08-29 07:51:13 +0200 Stefan Sauer + + * mediainfo/README: + * mediainfo/TODO: + todo: update planning and ideas + +2013-08-28 21:27:36 +0200 Stefan Sauer + + * mediainfo/src/mi-app.vala: + mi-app: use the newer gtk api with orientation + +2013-08-28 20:26:54 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: improve wikilink mapping + Try codecname and then caps name to get wiki links. Also show caps for the container. + +2013-08-27 23:57:06 +0200 Stefan Sauer + + * mediainfo/configure.ac: + * mediainfo/src/mi-info.vala: + mi: port to gst-1.0 and gtk+3 + +2013-08-18 16:01:33 +0200 Stefan Sauer + + * mediainfo/TODO: + TODO: some link for inspiration + +2012-10-23 15:54:06 +0200 Stefan Sauer + + * mediainfo/src/mi-app.vala: + * mediainfo/src/mi-info.vala: + cleanup. update name and year, queue a redraw for album art + +2012-10-23 15:21:34 +0200 Stefan Sauer + + * mediainfo/src/mi-app.vala: + * mediainfo/src/mi-info.vala: + porting: update on vala changes + +2011-06-21 15:05:37 +0200 Stefan Sauer + + * mediainfo/autogen.sh: + * mediainfo/configure.ac: + buid: fix the bootstrapping + Create the macrodir. Remove the GETTXT macro that was clashing with INTLTOOL. + +2011-03-04 18:14:06 +0200 Stefan Sauer + + * mediainfo/README: + README: more planning + +2011-03-04 18:13:34 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: add wikilinks for two more codecs + +2011-02-18 17:36:30 +0200 Stefan Sauer + + * mediainfo/README: + README: planning + +2011-01-29 14:41:28 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: reset container and duration fields if file is not discoverable + Before the previous text was left. + +2011-01-25 15:07:07 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: change the order of asyn disco calls + Starting disco before enqueueuing uris seems to make it work. + +2011-01-25 14:52:05 +0200 Stefan Sauer + + * mediainfo/README: + planing: CBR/VBR info + +2011-01-24 23:42:15 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: prepare for async discovery + The async api usage is not yet activated due to some uncertanty in the api use. + +2011-01-24 23:40:23 +0200 Stefan Sauer + + * mediainfo/README: + * mediainfo/src/mi-info.vala: + comments: planning and code comments + +2011-01-24 22:28:32 +0200 Stefan Sauer + + * mediainfo/src/Makefile.am: + Makefile: indenting + +2011-01-24 11:24:26 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: add more wikilinks + +2011-01-17 23:09:54 +0200 Stefan Sauer + + * mediainfo/configure.ac: + release: bump versions and back to development + +2011-01-17 23:05:54 +0200 Stefan Sauer + + * mediainfo/NEWS: + release: prepare for release + +2011-01-17 23:06:07 +0200 Stefan Sauer + + * mediainfo/HACKING: + docs: more maintainer info + +2011-01-16 14:25:19 +0200 Stefan Sauer + + * mediainfo/HACKING: + docs: add simple HACKING file + +2011-01-14 23:15:42 +0200 Stefan Sauer + + * mediainfo/src/Makefile.am: + build: fix distcheck + +2011-01-14 23:01:08 +0200 Stefan Sauer + + * mediainfo/src/mi-app.vala: + app: add idea for stream-open dialog + +2011-01-14 22:41:20 +0200 Stefan Sauer + + * mediainfo/configure.ac: + * mediainfo/po/POTFILES.in: + * mediainfo/src/Makefile.am: + * mediainfo/src/gst-mi.desktop.in: + desktop: add a desktop file + +2011-01-14 22:40:03 +0200 Stefan Sauer + + * mediainfo/src/Makefile.am: + * mediainfo/src/gst-mi.png: + * mediainfo/src/gst-mi.svg: + * mediainfo/src/mi-app.vala: + icon: add an application icon + Set as default icons, so that it it used for windows and in about dialog. + +2011-01-14 21:53:34 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: enable double buffering when displaying album art + +2011-01-14 18:39:01 +0200 Stefan Sauer + + * mediainfo/README: + * mediainfo/src/mi-info.vala: + info: handle album-art + Decode and draw the album art into the video window. + +2011-01-14 11:19:16 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: filter buffers from tags and add some planning comments + +2011-01-14 11:18:45 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: add comment with wikipedia alternative for links + +2011-01-13 12:21:46 +0200 Stefan Sauer + + * mediainfo/src/mi-app.vala: + app: add about dialog + +2011-01-13 12:05:35 +0200 Stefan Sauer + + * mediainfo/po/POTFILES.in: + * mediainfo/po/POTFILES.skip: + i18n: manage translatable files + +2011-01-13 12:03:00 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + * mediainfo/vapi/vapi.gstreamer-pbutils-0.10.patch: + vapi: no more need for patching the vapi file + +2011-01-13 11:56:37 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: switch stream callback for compact layout + +2011-01-13 11:56:18 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: verified link + +2011-01-13 10:03:32 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: add compact_layout mode + For screen-heights <= 600 pixels use a single notebook for all streams. + +2011-01-13 09:15:04 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: add more wikilinks and use it for streams too + +2011-01-12 10:17:56 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: add a hashmap with wiki links and start using them + The container description will be turned into a link if we have a known + wikipedia article for it. + +2011-01-04 14:56:40 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + mi-info: more ui layout planning + +2010-12-21 23:09:43 +0200 Stefan Sauer + + * mediainfo/README: + README: planning + +2010-12-21 14:54:56 +0200 Stefan Sauer + + * mediainfo/src/mi-app.vala: + menu: use alternative way to get the key number + +2010-12-21 13:55:02 +0200 Stefan Sauer + + * mediainfo/src/mi-app.vala: + menu: add F11 accelerator for fullscreen + +2010-12-21 13:21:34 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: blacklist "norminal-bitrate" too + We show that above already. + +2010-12-21 13:18:38 +0200 Stefan Sauer + + * mediainfo/src/mi-app.vala: + menu: add View menu with fullscreen item + +2010-12-21 12:01:40 +0200 Stefan Sauer + + * mediainfo/src/mi-app.vala: + * mediainfo/src/mi-info.vala: + layout: improve the layout on small screens + Pack the info view info a scrolled window. Use an extra paned to allow resizing + the video pane. Minimize padding on paned widgets. + +2010-12-21 11:27:57 +0200 Stefan Sauer + + * mediainfo/Makefile.am: + * mediainfo/configure.ac: + build: updates for gettext + +2010-12-16 23:06:57 +0200 Stefan Sauer + + * mediainfo/README: + README: planning + +2010-12-16 23:02:31 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: show the fps as a double + 23.97 fps is easier to read that 10000000 / 417083. + +2010-12-16 11:42:34 +0200 Stefan Sauer + + * mediainfo/autogen.sh: + autogen.sh: fix silly typo + +2010-12-15 11:46:08 +0200 Stefan Sauer + + * mediainfo/README: + README: planning + +2010-11-30 12:20:44 +0200 Stefan Sauer + + * mediainfo/README: + README: update todo lists + +2010-11-30 11:19:35 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: make urls in tags clickable + +2010-11-30 11:18:37 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: make labels selectable for copy'n'paste + +2010-11-10 08:50:48 +0200 Stefan Sauer + + * mediainfo/README: + * mediainfo/src/mi-info.vala: + planning: comments and todos + +2010-11-08 11:52:11 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: improve tag filtering + Hide duration as well. Also skip all tags where the name starts with "private-". + +2010-11-08 11:40:06 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: redo tag list formatting + Loop over tags and serialize items. Skip some already shown info. + +2010-11-08 10:38:39 +0200 Stefan Sauer + + * mediainfo/README: + * mediainfo/src/mi-app.vala: + * mediainfo/src/mi-info.vala: + maintenance: code cleanups and addition TODO: comments + +2010-11-05 17:17:16 +0200 Stefan Sauer + + * mediainfo/configure.ac: + * mediainfo/src/mi-info.vala: + info: add named video resolutions + Use a gee hashmap for named video resolutions (e.g. VGA) and show those in the + info. + +2010-11-04 12:47:14 +0200 Stefan Sauer + + * mediainfo/README: + * mediainfo/src/mi-info.vala: + * mediainfo/vapi/vapi.gstreamer-pbutils-0.10.patch: + info: show human readable container format name + Update the patch for vala bindings. Update README as getting the contaienr caps + is already possible. + +2010-11-04 10:49:40 +0200 Stefan Sauer + + * mediainfo/vapi/vapi.gstreamer-pbutils-0.10.patch: + vapi: add current patch for gstreamer-pbutils vapi metadata + +2010-11-04 10:46:43 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: show stream tags as multiline label + +2010-11-04 10:15:19 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: show misc stream info, if available + +2010-11-04 09:48:55 +0200 Stefan Sauer + + * mediainfo/src/mi-app.vala: + * mediainfo/src/mi.vala: + app: add directory property and use it as default location for browsing + Allow passing a directory as a commandline arg. If given use that as the default + location, otherwise use current working dir. + +2010-11-04 09:46:58 +0200 Stefan Sauer + + * mediainfo/src/mi.vala: + mi: we need to open the default display + The ui was crashing otherwise. Opening the default display seems to be the + default behaviour for gtk_init(). + +2010-11-03 13:59:44 +0200 Stefan Sauer + + * mediainfo/src/mi.vala: + mi: add basic goption usage + Only --version works right now. + +2010-11-03 10:47:04 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: handle video area expose also if we have no video + +2010-11-03 10:34:57 +0200 Stefan Sauer + + * mediainfo/README: + README: add todo for gst-discoverer + +2010-11-03 10:33:43 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: use File.query to get file info + Use the file info to query content-type and icon. Show file-type icon in the UI. + +2010-11-03 10:01:04 +0200 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: prepare to show human readable container format info + gst-discoverer does not yet provide it unfortunately. + +2010-10-28 17:34:38 +0300 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: add more TODO + +2010-10-28 17:34:15 +0300 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: switch streams when switching tabs + +2010-10-28 17:33:54 +0300 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: whitespace fix + +2010-10-28 16:45:55 +0300 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: add human readable code info + +2010-10-28 16:39:35 +0300 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: improve resolution + Orint it as "w x h" and add idea to convert to human readable string + +2010-10-28 16:34:08 +0300 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: add remaining audio/video info fields + +2010-10-28 03:08:28 +0300 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: turn stream widgets into tables and add bitrate + +2010-10-28 02:15:56 +0300 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: initialy paint video area black + +2010-10-28 01:41:48 +0300 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: ellipsize labels to avoid horzontal window growth + +2010-10-28 01:41:07 +0300 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: stop playback on unrealize + Prevent "BadDrawable" errors. + +2010-10-27 02:18:17 +0300 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: ensure we have a native widnow for the drawing_area + Fixes BadID x errors. + +2010-10-27 02:02:15 +0300 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: add vapi info for ubuntu + +2010-10-25 10:54:07 +0300 Stefan Sauer + + * mediainfo/README: + REDME: planning + +2010-10-20 00:44:18 +0300 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: get a/mediainfo/v streams and show caps + +2010-10-19 23:04:42 +0300 Stefan Sauer + + * mediainfo/src/mi-app.vala: + app: use a hpane instead of the file-chooser preview widget + This allows the user to modify the size. + +2010-10-19 22:53:28 +0300 Stefan Sauer + + * mediainfo/src/mi-app.vala: + * mediainfo/src/mi-info.vala: + app: only run discover for files + +2010-10-18 22:50:02 +0300 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: start to use discoverer and improve the ui + Organize the info pane as a table. Add mime type and duration fields to + container section. + +2010-10-18 17:38:32 +0300 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: add more ui boilerplate and fixme comments + +2010-10-18 17:14:46 +0300 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: enable discovered + Add comment telling how to regenerate the vapi files. + +2010-10-18 10:31:43 +0300 Stefan Sauer + + * mediainfo/src/mi-info.vala: + info: make the overlay iface work and activate playback + +2010-10-18 09:53:45 +0300 Stefan Sauer + + * mediainfo/src/mi-app.vala: + app: configure the filechooser more. + Go to home dir (maybe annoying feature). Don't show hidden files. + +2010-10-18 09:51:48 +0300 Stefan Sauer + + * mediainfo/src/Makefile.am: + build: fix libraryname + +2010-10-18 00:07:02 +0300 Stefan Sauer + + * mediainfo/src/Makefile.am: + * mediainfo/src/mi-info.vala: + info: add missing libs and enable the overlay code + +2010-10-17 23:54:06 +0300 Stefan Sauer + + * mediainfo/gst-mediainfo.anjuta: + anjuta: add anjuta project + +2010-10-17 23:53:23 +0300 Stefan Sauer + + * mediainfo/src/mi-app.vala: + info: add playbin2 and overlay iface handling + +2010-10-17 23:52:37 +0300 Stefan Sauer + + * mediainfo/src/mi-app.vala: + * mediainfo/src/mi-info.vala: + * mediainfo/src/mi.vala: + mi: init gst + +2010-10-17 23:21:16 +0300 Stefan Sauer + + * mediainfo/configure.ac: + * mediainfo/src/mi-info.vala: + info: start adding gstreamer discoverer + +2010-10-17 22:37:26 +0300 Stefan Sauer + + * mediainfo/src/mi.vala: + mi: code style + +2010-10-17 22:36:39 +0300 Stefan Sauer + + * mediainfo/src/Makefile.am: + * mediainfo/src/mi-app.vala: + * mediainfo/src/mi-info.vala: + info,app: move label to separate info class + Info class will run discover and have the detailed UI. + +2010-10-17 22:35:50 +0300 Stefan Sauer + + * mediainfo/README: + README: add sample discover output + +2010-10-17 22:16:27 +0300 Stefan Sauer + + * mediainfo/src/mi-app.vala: + * mediainfo/src/mi.vala: + app: add basic widget and do basic preview + Add a menu bar and a file browser. Set up a preview callback. + +2010-10-17 00:18:20 +0300 Stefan Sauer + + * mediainfo/Makefile.am: + * mediainfo/po/LINGUAS: + po: add LINGUAS file and handle generated files under po/ + +2010-10-16 23:56:55 +0300 Stefan Sauer + + * mediainfo/autogen.sh: + build: tweak autogen.sh + Generate autoregen.sh, run configure. + +2010-10-16 23:48:38 +0300 Stefan Sauer + + * mediainfo/AUTHORS: + * mediainfo/COPYING: + * mediainfo/ChangeLog: + * mediainfo/Makefile.am: + * mediainfo/NEWS: + * mediainfo/autogen.sh: + * mediainfo/configure.ac: + * mediainfo/git.mk: + * mediainfo/po/POTFILES.in: + * mediainfo/po/POTFILES.skip: + * mediainfo/src/Makefile.am: + * mediainfo/src/mi-app.vala: + * mediainfo/src/mi.vala: + * mediainfo/vapi/Makefile.am: + * mediainfo/vapi/config.vapi: + *: initial boilerplate + +2010-10-16 22:43:20 +0300 Stefan Sauer + + * mediainfo/README: + docs: start collecting ideas + +2013-09-13 11:43:33 -0300 Thibault Saunier + + * validate/data/seek_forward_backward.scenario: + * validate/data/simple_backward.scenario: + * validate/gst/validate/gst-validate-scenario.c: + data: Port remaning scenario files to new format + And add support to user declared timestamps -1.0 as GST_CLOCK_TIME_NONE + +2013-09-09 19:04:48 -0300 Thibault Saunier + + * validate/data/alternate_fast_backward_forward.scenario: + * validate/data/fast_backward.scenario: + * validate/data/fast_forward.scenario: + * validate/data/pause_resume.scenario: + * validate/data/seek_backward.scenario: + * validate/data/seek_forward.scenario: + * validate/data/simple_seeks.scenario: + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/gst/validate/validate.c: + scenario: Rework scenarios to be: 1- Simpler to write them, 2- extendible + Make the scenario files a list of GstStructure-s as strings + +2013-09-09 19:05:24 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-bin-monitor.c: + bin-monitor: Add a way to specify pipelines on which to set scenarios + When used with LD_PRELOAD, the application might use various pipelines + for several different thing, we need to make it possible to spcify a + specific pipeline (or set of pipelines) on which to run the scenario. + The format is in the form of: + scenario_name:pipelinename_pattern* + +2013-09-09 19:01:44 -0300 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Try to run scenarios in development first + +2013-09-09 17:40:36 +0200 Edward Hervey + + * .gitmodules: + * common: + * validate/autogen.sh: + * validate/common: + Adapt submodule usage for gst-devtools + +2013-09-02 15:42:40 +0200 Edward Hervey + + * validate/tools/.gitignore: + tools: Update .gitignore for tools move + +2013-09-05 16:15:40 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: avoid false positives when a seek fails + Remove the expected seqnums for events when a seek fails, preventing + false positives at the final report + +2013-09-05 04:34:42 -0400 Vincent Penquerc'h + + * validate/gst/validate/gst-validate-monitor-preload.c: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-runner.h: + * validate/tools/gst-validate.c: + monitor-preload: schedule a report printout at exit + Conflicts: + tools/gst-validate.c + +2013-09-04 11:09:50 -0400 Vincent Penquerc'h + + * validate/tools/gst-validate.c: + gst-validate: ensure the top level element is a pipeline + For instance, "fakesrc" will return a fakesrc, not a pipeline. + This is similar to what gst-launch does, and avoids calling + pipeline API on a non pipeline object (and thus asserting). + +2013-09-04 11:05:48 -0400 Vincent Penquerc'h + + * validate/tools/gst-validate.c: + gst-validate: do not try to use a pipeline which failed to create + Instead, error out properly with the actual error, if available. + +2013-09-04 10:50:11 -0400 Vincent Penquerc'h + + * validate/tools/gst-validate.c: + gst-validate: initialize gst/glib before use in scenario listing + Also ensure that if just -l is passed, we don't try creating a + non existent pipeline. + This makes gst-validate -l work properly again. + +2013-09-05 11:47:21 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: fix typo on macro usage + Pass the correct variable to macro + +2013-09-05 11:46:46 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: allow flushing flow returns when pad is flushing + It should always be acceptable to return GST_FLOW_FLUSHING when the + pad is flushing + +2013-09-03 15:58:20 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: removing bad check + Elements are allowed to accumulate segments, they don't have to push + 1:1 segments as they receive + +2013-09-03 15:35:36 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.h: + pad-monitor: buffer timestamp ranges check + Improve buffer timestamp range check: + * Only do it for encoders or decoders + * Audio has an acceptable tolerance of 100ms + To do this, keep track of the caps on the pad and store + if it is dealing with audio or video + +2013-09-03 15:17:05 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: move caps check to common event handling + Allows both src and sink pad to keep track of the current caps, but + the duplicated caps check is still only applied to sink pads as + src pads can push the same caps multiple times when it isn't linked + +2013-09-02 20:41:35 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: fix reference handling for expired events list + +2013-09-02 16:08:19 -0300 Thiago Santos + + * validate/README: + * validate/docs/validate-usage.txt: + docs: update and improve + Thanks to Thibault Saunier for most of the explanatory texts + +2013-09-02 13:22:51 -0300 Thiago Santos + + * validate/README: + * validate/data/Makefile.am: + * validate/docs/qa-usage.txt: + * validate/docs/validate-design.txt: + * validate/docs/validate-usage.txt: + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-element-monitor.c: + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-scenario.c: + Replacing mentions of qa with validate + +2013-09-02 12:18:07 -0300 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: Check if iterator exists before trying to use it + +2013-09-02 12:15:24 -0300 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: make debug log more readable + Use pad as the debug object to make logs more meaningful. + Also adds a FIXME note + +2013-09-02 12:11:25 -0300 Edward Hervey + + * validate/gst/validate/gst-validate-element-monitor.c: + element-monitor: protect agains elements that have no klass + +2013-09-02 11:37:02 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.h: + pad-monitor: use activate-mode function to detect when to clear pad data + Clear as much as a flush-stop when pad is deactivated + +2013-08-23 09:15:29 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.h: + WIP: pad-monitor: Fix serialized event order check + +2013-09-02 10:46:55 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.h: + pad-monitor: also track eos event that should be emitted after a seek + When seeking out of the media file length, the element should push an + EOS with the same seqnum of the seek event + +2013-09-02 10:46:42 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-scenario.c: + scenario: add missing space + +2013-08-25 19:53:27 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-report.c: + * validate/tools/gst-validate.c: + validate: prettify output of results + Makes the result a bit more readable than a compact multi-line list. + FIXME: Figure out how to print the description of the issues (which can + spawn multiple lines) in a nice way. + +2013-08-29 14:27:34 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-scenario.c: + scenario: add missing line break after print + +2013-08-29 14:26:05 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: avoid tracking tag events + Tag events are hard to track and check if properly serialized because + they mutate too much inside elements. There is no reliable way currently + to match a tag event pushed into an element and another tag event + leaving the element (other than if the pointers are actually the same). + +2013-08-29 11:48:33 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: only do combined return checks for demuxers + Seems like the only place that gstreamer elements should really + care about it + +2013-08-29 11:47:58 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: add two useful macros for readability + Avoids using long macros and having to check for pad-monitor parent + existance + +2013-08-28 06:07:40 -0400 Vincent Penquerc'h + + * validate/gst/validate/gst-validate-media-info.c: + * validate/gst/validate/gst-validate-media-info.h: + media-info: add a track switching test + This test will find the first input selector with more than one + sink pad, and cycle through them till it gets back to the original + one. Five seconds between switches. The test checks that some data + was sent from the input selector when each of the sink pads was + selected. + +2013-08-23 09:58:58 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Print on stdout when we seek + +2013-08-23 09:39:05 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-scenario.c: + validate: Report an issue result of query state that position > duration + +2013-08-22 16:52:45 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Execute action whenever we pass the expected position + We know are sequential so whenever the wanted position is passed we + should execute the action. + This avoid issue with the tolerance when we have high rate playback + +2013-08-22 12:16:55 -0400 Thibault Saunier + + * validate/tools/gst-validate-transcoding.c: + validate: Dump pipeline for each state change + Ala gst-launch + +2013-08-22 11:17:26 -0400 Thibault Saunier + + * validate/tools/gst-validate-transcoding.c: + transcoding: Print duration regularly + +2013-08-22 10:51:49 -0400 Thibault Saunier + + * validate/tools/gst-validate.c: + validate: Print state changes to help debugging + +2013-08-28 16:58:11 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-default-overrides.c: + * validate/gst/validate/gst-validate-element-monitor.c: + * validate/gst/validate/gst-validate-media-info.c: + * validate/gst/validate/gst-validate-monitor-factory.c: + * validate/gst/validate/gst-validate-monitor-preload.c: + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/gst-validate-override.c: + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/validate.c: + * validate/tools/gst-validate-media-check.c: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + licenses: improving licensing info on all files + +2013-08-28 16:49:07 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: fix typo when acessing parents data + +2013-08-27 18:23:09 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: reset buffer timestamp data after a flush + As the pad/element also clears its internal state + +2013-08-27 16:16:08 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: also track flush events on probes + +2013-08-27 11:56:33 -0300 Thiago Santos + + * validate/Makefile.am: + * validate/autogen.sh: + * validate/configure.ac: + * validate/gst/validate/Makefile.am: + * validate/tools/Makefile.am: + * validate/tools/gst-validate-media-check.c: + * validate/tools/gst-validate-transcoding.c: + * validate/tools/gst-validate.c: + tools: moving applications from gst/validate to tools + Keeps the CLI applications separate from the libs files + +2013-08-27 05:15:19 -0400 Vincent Penquerc'h + + * validate/gst/validate/gst-validate-scenario.c: + gst-validate-scenario: fix scenario listing missing installed ones + Only scenarii in the current directory or the user's home directory + were being listed. + +2013-08-27 05:08:46 -0400 Vincent Penquerc'h + + * validate/gst/validate/gst-validate-transcoding.c: + gst-validate-transcoding: fix help text to refer to URIs as URIs + Referring to them as files is confusing, as you'll try to use files + and not URIs. + +2013-08-27 04:38:52 -0400 Vincent Penquerc'h + + * validate/docs/qa-design.txt: + * validate/docs/qa-usage.txt: + docs: minor spelling/grammar fixes + +2013-08-27 11:48:00 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: Move repeated caps to test only on sinkpads + Testing on source pads can lead to false positives when pads are + unlinked. The caps event is sticky and will be pushed again later + when another buffer/event is pushed, leading to an acceptable + situation to push the caps twice. + +2013-08-26 20:30:07 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-element-monitor.c: + * validate/gst/validate/gst-validate-element-monitor.h: + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: add another acceptable flow return combination scenarios + A demuxer knows when to return EOS after samples are over, so it is + ok for it to return even when all src pads returned OK + +2013-08-26 18:38:27 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: improve serialized event checks + If the event was already found at the first position of the array, it + shouldn't be searched on the rest of it. + This removes lots of false positives. + +2013-08-26 18:36:06 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: fix aggregate flow return check for error situations + Flow flushing must be returned upstream to indicate an error situation + downstream + +2013-08-26 20:31:22 -0300 Thiago Santos + + * validate/gst/validate/gst-validate.c: + gst-validate: print error message when starting the pipeline fails + Instead of just exiting silently + +2013-08-23 09:16:43 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.h: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + pad-monitor: New check for duplicate caps event + We shouldn't get/push twice caps that are identical + +2013-08-23 17:26:51 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-media-info.c: + media-info: avoid glib assert + +2013-08-23 11:38:15 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-reporter.h: + * validate/gst/validate/gst-validate-scenario.c: + report: Avoid repeating long macros + Makes the code a bit more readable and compact + +2013-08-23 11:07:40 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-report.c: + validate-report: Fix critical flag handling + criticals are warnings/issues also + warnings are issues also + +2013-08-20 17:25:48 -0400 Thibault Saunier + + * validate/data/Makefile.am: + * validate/data/alternate_fast_backward_forward.scenario: + data: Add a test that alternates (fast) backward and forward playback + +2013-08-19 10:03:04 -0400 Thibault Saunier + + * validate/data/Makefile.am: + * validate/data/seek_backward.scenario: + * validate/data/seek_forward.scenario: + data: Add a seek_backward/forward scenarios + +2013-08-19 10:02:35 -0400 Thibault Saunier + + * validate/data/simple_seeks.scenario: + * validate/gst/validate/gst-validate-scenario.c: + scenario: Have GstClockTime as second (in double) inside scenario files + Making it easier to read + +2013-08-15 17:32:23 +0200 Thibault Saunier + + * validate/data/Makefile.am: + * validate/data/fast_backward.scenario: + * validate/data/fast_forward.scenario: + * validate/data/simple_backward.scenario: + data: Add fast_forward/backward and simple_backward scenarios + +2013-08-15 12:34:09 +0200 Thibault Saunier + + * validate/data/Makefile.am: + * validate/data/seek_forward_backward.scenario: + * validate/gst/validate/gst-validate-scenario.c: + data: Add a Backward and Forward seeking scenario + +2013-08-15 12:17:43 +0200 Thibault Saunier + + * validate/data/Makefile.am: + * validate/data/pause_resume.scenario: + data: Add a Pause/Resume scenario + +2013-08-19 14:13:10 -0400 Thibault Saunier + + * validate/gst/validate/gst-validate-transcoding.c: + * validate/gst/validate/gst-validate.c: + validate: Set return value of apps to -1 only if a critical issues was reported + Conflicts: + gst/validate/gst-validate-transcoding.c + gst/validate/gst-validate.c + +2013-08-16 16:41:50 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-transcoding.c: + transcoding: Make sure to initialize Gst before parsing options + Avoiding to break the help + +2013-08-15 15:59:22 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-transcoding.c: + transcoding: Connect to the bus signals watch as the main watch might already be connected + +2013-08-15 17:31:17 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Start monitoring the position only when the pipeline starts playing + Otherwize seeking with a playback_time=0 won't work properly + +2013-08-15 17:30:34 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Fix negative rate management + Properly parse the it has a gdouble and set the stop position of the seek as + seeked_position if the rate is negative + + Add some debug + +2013-08-15 12:33:23 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Actions order in xml file is the order in which they must be executed + When seeking we might want to execute seeks at a playback time inferior than previous + seek, so we need to be able to define the order in which actions have to be + executed, the simplest way is to just concider that actions are always + order in the XML files. + + Add some more debugs + Conflicts: + gst/validate/gst-validate-scenario.c + +2013-08-15 15:57:52 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + scenario: Rename the seeks list to actions, and initialize action to 0 when allocating + +2013-08-16 12:17:34 +0200 Thibault Saunier + + * validate/data/Makefile.am: + * validate/data/simple_seeks.scenario: + * validate/gst/validate/gst-validate-scenario.c: + scenario: Rename scenario xml files extension to .scenario + +2013-08-15 12:18:56 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate.c: + validate: Connect to the bus signals watch as the main watch might already be connected + +2013-08-16 12:50:51 +0200 Thibault Saunier + + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/gst/validate/gst-validate-transcoding.c: + * validate/gst/validate/gst-validate.c: + validate: Add a way to list avalaible scenarios + Conflicts: + gst/validate/gst-validate-transcoding.c + +2013-08-22 10:35:50 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-transcoding.c: + gst-validate-transcoding: add signal handling and issues printing + Update to have the same features as gst-validate. + 1) Handle interrupts properly, with the additional of having the + 'eos-on-shutdown' argument that sends EOS to the pipeline. This is + very useful for transcoding processes to finish correctly. + 2) Print issues on the end of application + +2013-08-22 10:08:13 -0300 Thiago Santos + + * validate/gst/validate/gst-validate.c: + gst-validate: add interrupt handler + Handle interrupt properly to still print issues when exiting + +2013-08-21 18:21:41 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: Fix source pad probe handling + type is a bitmask and not an enum + +2013-08-21 13:10:42 -0300 Thiago Santos + + * validate/gst/validate/gst-validate.c: + gst-validate: fix documentation after debug category changes + +2013-08-21 18:00:16 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-reporter.c: + validate-reporter: More comprehensive debug message + Some issues don't have any arguments, so put the full details in. + +2013-08-20 11:43:07 +0200 Edward Hervey + + * validate/gst/validate/Makefile.am: + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-element-monitor.c: + * validate/gst/validate/gst-validate-internal.h: + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/gst-validate-override.c: + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/validate.c: + validate: Only use one debugging category: validate + There's no point in having a different debug category per file, you + can filter it by source filename if you *really* want that. + +2013-08-21 12:11:40 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-media-check.c: + * validate/gst/validate/gst-validate.c: + gst-validate: print issues at the end + And improve documentation about usage + +2013-08-21 11:03:19 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-media-check.c: + * validate/gst/validate/gst-validate-media-info.c: + * validate/gst/validate/gst-validate-media-info.h: + media-check: add results file comparison + Adds a new expected-results argument to receive a file that is used + as a base for comparison with the new results. In case differences are + found, the application will print those issues. + +2013-08-20 17:10:44 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-reporter.c: + reporter: do not print issues to stdout + +2013-08-20 15:44:10 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-media-info.c: + media-info: fix playback tests + They weren't waiting for the pipeline to properly change state + before sending seek events, that would cause some events to + return TRUE even if they were not handled + +2013-08-20 15:42:54 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-media-check.c: + media-check: return nonzero if a test failed + +2013-08-20 13:24:31 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-media-info.c: + * validate/gst/validate/gst-validate-media-info.h: + media-info: add playback and reverse-playback tests + The tests are very simple as they only write the first error they + found during playback. If no error is set, an empty string is + printed. + The playback pipeline isn't monitored with validate monitors for now + +2013-08-20 11:43:06 -0300 Thiago Santos + + * validate/gst/validate/Makefile.am: + * validate/gst/validate/gst-validate-media-check.c: + rename: gst-validate-file-check -> gst-validate-media-check + It not only validates files, takes any URI + +2013-08-20 11:41:15 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-media-info.c: + * validate/gst/validate/gst-validate-media-info.h: + media-info: add stream topology parsing + Currently it only saves/loads the main type, but all topology is + already being parsed for future use + +2013-08-19 16:52:12 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-media-info.c: + media-info: add duration and seekable entries + Add duration entry in ns and seekable as a boolean to a new group + 'media-info' + +2013-08-19 16:38:13 -0300 Thiago Santos + + * validate/gst/validate/Makefile.am: + * validate/gst/validate/gst-validate-file-check.c: + * validate/gst/validate/gst-validate-file-checker.h: + * validate/gst/validate/gst-validate-media-info.c: + * validate/gst/validate/gst-validate-media-info.h: + * validate/gst/validate/gst-validate-transcoding.c: + * validate/gst/validate/validate.h: + media-info: replacing file-checker with a simpler media-info struct + This struct stores information about a media and tests run on it. It + also has a few helper functions that allows storing the results to a + file and loading it back. + Instead of having the file-checker object that would compare the + extracted values from the file to expected results set to its properties, + the media-info will store the values and it will be possible to compare + old media-info with new media-info from the same file. This allows + tracking improvements and regressions on different gstreamer versions. + Right now, the media-info is very tiny and doesn't store much info, only + the uri and the file size in bytes, but it will receive more additions in + the upcoming commits for storing duration, media topology, seekability and + playback information. + +2013-08-16 15:15:51 +0200 Edward Hervey + + * validate/.gitignore: + * validate/gst/validate/.gitignore: + .gitignore: Update for 1.0 and cleanup + +2013-08-16 15:05:54 +0200 Edward Hervey + + * validate/configure.ac: + * validate/gst/validate/gst-validate-bin-monitor.h: + * validate/gst/validate/gst-validate-default-overrides.c: + * validate/gst/validate/gst-validate-element-monitor.h: + * validate/gst/validate/gst-validate-monitor-factory.h: + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-monitor.h: + * validate/gst/validate/gst-validate-override-registry.h: + * validate/gst/validate/gst-validate-override.h: + * validate/gst/validate/gst-validate-pad-monitor.h: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-reporter.h: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-runner.h: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/gst/validate/validate.h: + all: Enable more C warnings at build time + And fix the issues: + * Proper forward declaration + * static functions marked properly + * absolute includes + * declaration order + +2013-08-16 14:27:29 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-reporter.c: + reporter: Fix proper debug message output partially + In order for the special gstreamer print argument handler to be used + you can't use g_strdup_printf. You need to pass it the actual va_list. + +2013-08-16 14:26:35 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: Handle case where internal pad iterator is NULL + Can happen with inputselector + +2013-08-16 14:25:49 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: Don't use signal that doesn't exist + Note that we should just ensure we always get the pads from the parent + +2013-08-16 14:24:12 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: Update raw audio caps checks + +2013-08-16 14:23:05 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-monitor.h: + * validate/gst/validate/gst-validate-pad-monitor.c: + pad-monitor: Fix locking issues + We were taking locks twice. + Also add debugging info when taking/releasing locks to help further similar issues + +2013-08-16 11:24:11 +0200 Edward Hervey + + * validate/gst/validate/gst-validate-file-checker.c: + file-checker: GstEncodingProfile is a GObject in 1.0 + +2013-08-15 01:46:27 -0300 Thiago Santos + + * validate/configure.ac: + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-element-monitor.c: + * validate/gst/validate/gst-validate-file-checker.c: + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.h: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-transcoding.c: + gst-validate: port to 1.0 + +2013-08-15 01:44:59 -0300 Thiago Santos + + * validate/po/POTFILES.in: + po: missing po rename + +2013-08-14 20:03:43 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-element-monitor.c: + * validate/gst/validate/gst-validate-monitor-factory.c: + * validate/gst/validate/gst-validate-monitor-preload.c: + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/gst-validate-override.c: + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-runner.c: + validade: add missing config.h includes + +2013-08-14 19:14:18 -0300 Thiago Santos + + * validate/gst/validate/Makefile.am: + * validate/gst/validate/gst-validate-file-check.c: + * validate/gst/validate/gst-validate-monitor-preload.c: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-transcoding.c: + * validate/gst/validate/gst-validate.c: + * validate/gst/validate/validate.c: + * validate/gst/validate/validate.h: + validate: add init function + Adds an init() function that should be called before using the lib. + It takes care of calling all internal initializing functions in + gst-validete + +2013-08-14 18:04:23 -0300 Thiago Santos + + * validate/gst/validate/gst-validate-file-check.c: + * validate/gst/validate/gst-validate-transcoding.c: + * validate/gst/validate/gst-validate.c: + tools: improve documentation + +2013-08-14 16:30:39 -0300 Thiago Santos + + * validate/autogen.sh: + * validate/configure.ac: + * validate/gst/Makefile.am: + * validate/gst/qa/.gitignore: + * validate/gst/qa/Makefile.am: + * validate/gst/qa/gst-qa-bin-monitor.h: + * validate/gst/qa/gst-qa-element-monitor.c: + * validate/gst/qa/gst-qa-element-monitor.h: + * validate/gst/qa/gst-qa-file-checker.h: + * validate/gst/qa/gst-qa-monitor.c: + * validate/gst/qa/gst-qa-monitor.h: + * validate/gst/qa/gst-qa-override-registry.c: + * validate/gst/qa/gst-qa-override.h: + * validate/gst/qa/gst-qa-report.c: + * validate/gst/qa/gst-qa-report.h: + * validate/gst/qa/gst-qa-reporter.c: + * validate/gst/qa/gst-qa-reporter.h: + * validate/gst/qa/gst-qa-runner.h: + * validate/gst/qa/gst-qa-scenario.h: + * validate/gst/qa/qa.h: + * validate/gst/validate/.gitignore: + * validate/gst/validate/Makefile.am: + * validate/gst/validate/gettext.h: + * validate/gst/validate/gst-validate-bin-monitor.c: + * validate/gst/validate/gst-validate-bin-monitor.h: + * validate/gst/validate/gst-validate-default-overrides.c: + * validate/gst/validate/gst-validate-element-monitor.c: + * validate/gst/validate/gst-validate-element-monitor.h: + * validate/gst/validate/gst-validate-file-check.c: + * validate/gst/validate/gst-validate-file-checker.c: + * validate/gst/validate/gst-validate-file-checker.h: + * validate/gst/validate/gst-validate-i18n-lib.h: + * validate/gst/validate/gst-validate-monitor-factory.c: + * validate/gst/validate/gst-validate-monitor-factory.h: + * validate/gst/validate/gst-validate-monitor-preload.c: + * validate/gst/validate/gst-validate-monitor.c: + * validate/gst/validate/gst-validate-monitor.h: + * validate/gst/validate/gst-validate-override-registry.c: + * validate/gst/validate/gst-validate-override-registry.h: + * validate/gst/validate/gst-validate-override.c: + * validate/gst/validate/gst-validate-override.h: + * validate/gst/validate/gst-validate-pad-monitor.c: + * validate/gst/validate/gst-validate-pad-monitor.h: + * validate/gst/validate/gst-validate-report.c: + * validate/gst/validate/gst-validate-report.h: + * validate/gst/validate/gst-validate-reporter.c: + * validate/gst/validate/gst-validate-reporter.h: + * validate/gst/validate/gst-validate-runner.c: + * validate/gst/validate/gst-validate-runner.h: + * validate/gst/validate/gst-validate-scenario.c: + * validate/gst/validate/gst-validate-scenario.h: + * validate/gst/validate/gst-validate-transcoding.c: + * validate/gst/validate/gst-validate.c: + * validate/gst/validate/validate.h: + rename gst-qa -> gst-validate + +2013-08-14 15:58:34 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: only do complete caps checks on setcaps + On get caps it is acceptable to have missing fields to simplify caps + negotiation + +2013-08-13 13:40:48 -0300 Thiago Santos + + * validate/gst/qa/Makefile.am: + qa-preload: split to separate lib + It should only be used separately, otherwise it will wrap around any + pipeline from applications linking with gstqa + +2013-08-12 15:18:36 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-file-check.c: + * validate/gst/qa/gst-qa-file-checker.c: + * validate/gst/qa/gst-qa-file-checker.h: + file-check: add reverse-playback test + Adds a test that checks if reverse playback works without errors + +2013-08-13 11:07:31 +0200 Edward Hervey + + * validate/gst/qa/gst-qa-reporter.c: + qa-reporter: Make debug message a bit more readable + By surrounding it with double quotes + +2013-08-13 11:07:05 +0200 Edward Hervey + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: 0.10 uses "channel-positions" field in audio caps + And it's an array, not a string + +2013-08-13 10:11:42 +0200 Edward Hervey + + * validate/Makefile.am: + * validate/gst/qa/Makefile.am: + Makefile: Clean up for make distcheck + Directories, headers, files weren't properly disted + Also clean up the various CFLAGS/HEADERS/SOURCES variables and remove + ones that aren't needed. + +2013-08-13 09:44:50 +0200 Edward Hervey + + * validate/po/Makevars: + po: Add missing Makevars file + +2013-08-09 12:37:49 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: use correct variable for segment comparisons + Use the expected versus the received instead of using the received + twice. + +2013-08-09 12:33:27 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-reporter.c: + reporter: fix printf format type + +2013-08-08 12:35:50 -0300 Thiago Santos + + * validate/README: + * validate/docs/qa-design.txt: + * validate/docs/qa-usage.txt: + docs: improve and update docs + +2013-08-07 17:31:17 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-bin-monitor.c: + * validate/gst/qa/gst-qa-bin-monitor.h: + * validate/gst/qa/gst-qa-runner.c: + * validate/gst/qa/gst-qa-runner.h: + qa-scenario: re add scenarios creation to bin-monitor + GstPipelines are monitored by bin monitors. Create scenarios if + requested from the bin monitors and store them there. + +2013-08-07 16:22:36 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-monitor.c: + * validate/gst/qa/gst-qa-monitor.h: + * validate/gst/qa/gst-qa-reporter.h: + qa-monitor: remove reference to the runner + qa-monitor implements qa-reporter, and we already have a runner stored + there. + +2013-08-07 16:13:33 -0300 Thiago Santos + + * validate/gst/qa/.gitignore: + gitignore: ignore more binaries + +2013-08-07 16:12:45 -0300 Thiago Santos + + * validate/gst/qa/Makefile.am: + * validate/gst/qa/gst-qa-file-check.c: + qa-file-check: add new binary to run file checks easily + It creates a GstQaFileChecker and runs it on the passed URI with + the tests enabled as arguments + +2013-08-07 16:10:57 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-monitor-preload.c: + * validate/gst/qa/gst-qa-runner.c: + * validate/gst/qa/gst-qa-runner.h: + * validate/gst/qa/gst-qa-scenario.c: + * validate/gst/qa/gst-qa-scenario.h: + * validate/gst/qa/gst-qa-transcoding.c: + * validate/gst/qa/gst-qa.c: + * validate/gst/qa/qa.h: + qa-runner: simplify runner to not hold refs to monitor/pipeline + The GstQaRunner is now a simple aggregator of reports that it receives + from monitors and filechecker. This allows it to be used in both + scenarios without APIs that expect GstElement or Monitors, that are + only used on the pipeline monitoring QA tests. + +2013-08-07 11:31:04 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-file-checker.c: + * validate/gst/qa/gst-qa-file-checker.h: + * validate/gst/qa/gst-qa-report.c: + * validate/gst/qa/gst-qa-report.h: + * validate/gst/qa/gst-qa-transcoding.c: + file-checker: add file playback testing feature + Adds a property that triggers the file playback tests on + GstQaFileCheker. Also enable it in the gst-transcoding post file checks. + The implementation is simple, just create a playbin2 and use fakesinks + as sinks, set it to playing and wait for either EOS or ERROR messages. + +2013-08-06 19:42:21 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-reporter.h: + qa-reporter: fix typo + +2013-08-06 19:39:58 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-file-checker.c: + file-checker: include restriction caps tests when checking for profiles + Also move the caps check earlier on the path, to error out sooner and + avoid iterating the sub streams without needing + +2013-08-06 18:17:39 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-file-checker.c: + file-checker: replace encoding profile comparison + Use our own custom comparison to allow to add more fine grained error + reporting. Also the encoding profile is_equal function is too strict as + it also compares profiles names, that doesn't matter to us. + This commit implementation is still initial and needs improvements as it + isn't using the restriction caps, which includes information that might not be + on the profile format caps. + +2013-08-06 10:36:58 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-reporter.c: + qa-reporter: fix crash by avoiding unref an integer + +2013-08-06 10:36:47 -0300 Thiago Santos + + * validate/gst/qa/Makefile.am: + makefile: fix build of gst-qa- tools + +2013-08-06 10:36:02 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-file-checker.c: + * validate/gst/qa/gst-qa-report.c: + * validate/gst/qa/gst-qa-report.h: + file-checker: add error report and new report types + Add a list of new report types and use them in the file-checker. + The errors are mostly related to testing file attributes against + expected values + +2013-08-05 14:16:06 -0300 Thiago Santos + + * validate/gst/qa/Makefile.am: + * validate/gst/qa/gst-qa-file-checker.c: + * validate/gst/qa/gst-qa-file-checker.h: + * validate/gst/qa/gst-qa-transcoding.c: + qa-file-checker: add a file checker object/runner + It is an object that is capable to run a few file checks. The + implemented tests are: file size, duration, if the file is seekable and + comparing the file stream types with a encoding profile + +2013-08-01 18:08:44 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-scenario.c: + qa-scenario: adding eos scenario action + Allows sending EOS to the pipeline + +2013-08-01 09:35:59 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-report.c: + * validate/gst/qa/gst-qa-report.h: + * validate/gst/qa/gst-qa-scenario.c: + qa-scenario: add new scenario action - Pause + The pause action instructs the pipeline to go to paused state and then + return to playing. It has the argument 'duration', that indicates the + duration for which the pipeline will remain in paused + +2013-08-01 01:27:20 -0300 Thiago Santos + + * validate/data/simple_seeks.xml: + * validate/gst/qa/gst-qa-scenario.c: + qa-scenario: refactor to accomodate more actions + Refactor to be able to reuse to add more actions to scenarios. + Planned are pauses and encoding changes + +2013-07-31 15:01:13 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-scenario.c: + qa-scenario: avoid assertion on dispose + After an error, the pipeline might still be null, check before unreffing + +2013-07-31 15:00:56 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-scenario.c: + qa-scenario: fix typo on define variable + +2013-07-31 15:00:33 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-report.c: + qa-report: fix typo on assertion comparison + +2013-07-31 11:12:41 +0100 Vincent Penquerc'h + + * validate/gst/qa/gst-qa-element-monitor.c: + gst-qa-element-monitor: do not bypass monitor factory + A pad monitor was created directly. Prefer going through the + factory. + +2013-07-31 11:05:05 +0100 Vincent Penquerc'h + + * validate/gst/qa/gst-qa-report.c: + gst-qa-reporter: fix use of uninitialized repeat field + +2013-07-31 11:04:32 +0100 Vincent Penquerc'h + + * validate/gst/qa/gst-qa-reporter.c: + gst-qa-reporter: fix report leak when discarding repeated report + +2013-07-31 10:49:48 +0100 Vincent Penquerc'h + + * validate/autogen.sh: + * validate/configure.ac: + * validate/po/POTFILES.in: + gst-qa: fix build in po + Using a lot of grep and some cargo culting. + +2013-07-31 10:07:53 +0100 Vincent Penquerc'h + + * validate/gst/qa/Makefile.am: + gst-qa: make tools depend on libraries + This fixes parallel build randomly breaking. + +2013-07-30 17:07:13 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-report.c: + * validate/gst/qa/gst-qa-report.h: + qa-report: expose API for adding custom issues + expose gst_qa_issue_register and gst_qa_issue_new to allow applications + to register their own custom issues. + Issues IDs should use Areas higher than GST_QA_AREA_OTHER for custom + areas. And to add more issues to existing areas, the IDs should be + higher than GST_QA_ISSUE_ID_CUSTOM_FIRST. + Custom issues registering should be done at startup and from the same + thread as there is no locking around the issues hashtable + +2013-07-30 16:21:15 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-transcoding.c: + * validate/gst/qa/gst-qa.c: + Fix typos + +2013-07-30 16:20:49 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-runner.c: + * validate/gst/qa/gst-qa-runner.h: + * validate/gst/qa/gst-qa-transcoding.c: + * validate/gst/qa/gst-qa.c: + qa-runner: Remove printing API from qa-runner + Replace it with functions to list the reports + +2013-07-30 12:17:48 -0400 Vincent Penquerc'h + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: fix NULL format string + An empty message should be an empty string. + +2013-07-30 10:21:13 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-monitor-preload.c: + * validate/gst/qa/gst-qa-runner.c: + * validate/gst/qa/gst-qa-runner.h: + * validate/gst/qa/gst-qa-transcoding.c: + * validate/gst/qa/gst-qa.c: + qa-runner: removing _setup call + Do setup on the _new function directly instead of having a separate + call for that + +2013-07-30 09:56:05 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-override.c: + * validate/gst/qa/gst-qa-override.h: + * validate/gst/qa/gst-qa-pad-monitor.c: + qa-override: add more pad overrides for buffer probe and caps + Add override functions for custom checking of buffer probe and + getcaps/setcaps functions. + +2013-07-29 17:26:21 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-monitor.c: + * validate/gst/qa/gst-qa-monitor.h: + * validate/gst/qa/gst-qa-override.c: + * validate/gst/qa/gst-qa-override.h: + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: call the event/query/buffer overrides + Use the new event/buffer/query overrides to allow custom checks + on those scenarios + +2013-07-29 16:26:52 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-override.c: + * validate/gst/qa/gst-qa-override.h: + qa-override: add callbacks for query/buffer/event functions + Add callbacks for pad event/buffer/query functions in case the + override wants to do additional checks + +2013-07-30 10:20:43 +0100 Vincent Penquerc'h + + * validate/gst/qa/Makefile.am: + * validate/gst/qa/gst-qa-default-overrides.c: + * validate/gst/qa/gst-qa-override-registry.c: + * validate/gst/qa/gst-qa-override-registry.h: + * validate/gst/qa/gst-qa-runner.c: + gst-qa-override-registry: load overrides dynamically + Shared objects listed in GST_QA_OVERRIDE are loaded on startup, + and the symbol gst_qa_create_overrides is run. It should create + any override needed. While it can do anything it wants, this + is discouraged. + GST_QA_OVERRIDE should be a comma separated list of shared objects, + any relative paths should be from the current working directory + at the time they are loaded (ie, if the process to be traced + changes cwd, use absolute paths). + No attempt whatsoever is made at not running what was not meant. + Includes a sample shared object for illustration purposes. + +2013-07-29 13:17:50 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-monitor.c: + * validate/gst/qa/gst-qa-monitor.h: + * validate/gst/qa/gst-qa-override-registry.c: + * validate/gst/qa/gst-qa-override-registry.h: + qa-override-registry: register overrides by gtype and klass + Overrides can now be registerd by gtype, meaning that they will + be attached to monitors that the target is of the requested type. + Also by element klass, that will check that the element has the + selected class in its details + +2013-07-29 12:01:02 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-monitor.c: + * validate/gst/qa/gst-qa-monitor.h: + qa-monitor: implement intercept_report + It is used to iterate over overrides and modify the report level if + the overrides wants to do so. + Also adds a new mutex only for the overrides to avoid deadlocks when + reporting if we used the same lock for iterating the overrides + +2013-07-29 11:35:20 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-reporter.h: + qa-reporter: fix copy n paste left over + +2013-07-29 11:34:42 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-report.c: + * validate/gst/qa/gst-qa-report.h: + * validate/gst/qa/gst-qa-reporter.c: + * validate/gst/qa/gst-qa-reporter.h: + qa-reporter: add function for intercepting reports + after report creation, this function is called and implementers can + modify the report to their liking before it is posted to the runner + +2013-07-29 09:37:46 -0400 Vincent Penquerc'h + + * validate/gst/qa/gst-qa-monitor.c: + qa-monitor: chain gst_qa_monitor_finalize to parent's finalize + It was chaining to the parent's dispose. + +2013-07-29 10:06:48 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-report.h: + gst-qa-report: put the correct format to avoid compiler warnings + +2013-07-29 07:02:30 -0400 Vincent Penquerc'h + + * validate/gst/qa/Makefile.am: + * validate/gst/qa/gettext.h: + * validate/gst/qa/gst-qa-i18n-lib.h: + * validate/gst/qa/gst-qa-report.c: + i18n: copy necessary files in-tree + +2013-07-29 07:20:50 -0400 Vincent Penquerc'h + + * validate/gst/qa/gst-qa-report.h: + GstIssueId: make this uintptr_t + As it's used a a placeholder pointer for g_hash_table use, + it needs to be converted back and forth to a pointer. + +2013-07-26 19:05:31 -0300 Thiago Santos + + * validate/gst/qa/Makefile.am: + * validate/gst/qa/gst-qa-bin-monitor.c: + * validate/gst/qa/gst-qa-element-monitor.c: + * validate/gst/qa/gst-qa-monitor-factory.c: + * validate/gst/qa/gst-qa-monitor-factory.h: + * validate/gst/qa/gst-qa-monitor.c: + * validate/gst/qa/gst-qa-monitor.h: + * validate/gst/qa/gst-qa-override-registry.c: + * validate/gst/qa/gst-qa-override-registry.h: + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-pad-monitor.h: + * validate/gst/qa/gst-qa-runner.c: + * validate/gst/qa/gst-qa-runner.h: + gst-qa-override-registry: adding the override-registry + This registry should contain the list of GstQaOverride to + be used on the pipelines being monitored + +2013-07-26 00:14:02 -0300 Thiago Santos + + * validate/gst/qa/Makefile.am: + * validate/gst/qa/gst-qa-override.c: + * validate/gst/qa/gst-qa-override.h: + qa-override: adds qa-override that can change the report level of issues + Useful for customizing the level of issues for particular elements/tests + when they are more relevant or have to be disabled + +2013-07-25 23:25:22 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-report.c: + * validate/gst/qa/gst-qa-report.h: + * validate/gst/qa/gst-qa-reporter.c: + * validate/gst/qa/gst-qa-reporter.h: + * validate/gst/qa/gst-qa-scenario.c: + qa-report: splitting a GstQaReport into a GstQaIssue and GstQaReport + Reports now point to Issues, that are uniquely identified and have + translatable descriptions. This way we are going to be able to uniquely + identify the issues and applications can enable/disable checks for + specific elements. + +2013-07-24 19:09:14 -0400 Thibault Saunier + + * validate/gst/qa/gst-qa-transcoding.c: + * validate/gst/qa/gst-qa.c: + qa: Make it possible to set a scenario from the command line in test apps + +2013-07-23 10:13:06 -0400 Thibault Saunier + + * validate/gst/qa/gst-qa-monitor.c: + * validate/gst/qa/gst-qa-reporter.c: + * validate/gst/qa/gst-qa-reporter.h: + * validate/gst/qa/gst-qa-scenario.c: + qa: Properly set reporter's runner reference + That was never set, but it is needed for the reporter to properly add + reports to the runner. + We still keep a reference on the monitor to make things simpler + +2013-07-23 08:55:24 -0400 Thibault Saunier + + * validate/gst/qa/gst-qa-reporter.c: + * validate/gst/qa/gst-qa-reporter.h: + reporter: Use Gst debugging log in the _report method directly + Using __VALIST__ was not properly working + + Add a gstqareporter debug category + +2013-07-22 19:22:49 -0400 Thibault Saunier + + * validate/gst/qa/gst-qa-report.c: + * validate/gst/qa/gst-qa-report.h: + * validate/gst/qa/gst-qa-reporter.c: + * validate/gst/qa/gst-qa-reporter.h: + * validate/gst/qa/gst-qa-runner.c: + * validate/gst/qa/gst-qa-scenario.c: + * validate/gst/qa/gst-qa-scenario.h: + scenario: Implement the GstQaReporter interface and make use of it + This way we can report issues from a scenario + Also add a Seek aread to the known areas list + We now need to pass the runner to the scenario instead of the + pipeline as the GstQaReporter interface needs it. + +2013-07-22 19:17:53 -0400 Thibault Saunier + + * validate/data/Makefile.am: + * validate/gst/qa/Makefile.am: + * validate/gst/qa/gst-qa-monitor.c: + * validate/gst/qa/gst-qa-monitor.h: + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-report.c: + * validate/gst/qa/gst-qa-report.h: + * validate/gst/qa/gst-qa-reporter.c: + * validate/gst/qa/gst-qa-reporter.h: + qa: Add a GstQaReporter interface that objects needing reporting can implement + Various type of object should be able to do some reporting, so we have + to make sure all the code to do that is in one place. Creating an interface + makes it simple to share information and it avoid to have a baseclass for + something that is not actually important enough to create a baseclass. + Conflicts: + gst/qa/gst-qa-pad-monitor.c + +2013-07-20 00:18:13 -0400 Thibault Saunier + + * validate/configure.ac: + * validate/data/Makefile.am: + * validate/data/simple_seeks.xml: + * validate/gst/qa/Makefile.am: + * validate/gst/qa/gst-qa-runner.c: + * validate/gst/qa/gst-qa-runner.h: + * validate/gst/qa/gst-qa-scenario.c: + * validate/gst/qa/gst-qa-scenario.h: + qa: Add a GstQaScenario class making it possible to execute scenarios + A scenario correspond to a suite of action to execute on a pipeline, + for the time being, we only support seeking the pipeline, but in the + future we can imagine doing some queries, setting pipeline state, etc... + The scenario can be loaded thanks to the GST_QA_SCENARIO environment + variable, making it usable with any existant application, in case, the + application can be used interactively, the user should either, not load + any scenario or let the application run without interacting with it. + +2013-07-24 16:04:03 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: add lots of locking + When handling elements that spawn multiple threads (hardware + enc/decoders), the pad monitor has to protect its variables specially + because some checks involve iterating over internally linked pads to + add/get some data for comparison (expected events, timestamp ranges, + caps). + Aside from locking its own mutex, the pad monitor can also lock the + parent's mutex when it needs to use data from its internally linked + pads. The locking order should always be parent and then individual + pad-monitor mutexes. This should prevent deadlocks when multiple + pad-monitors from the same element start doing checks at the same time + from different threads. + +2013-07-24 10:05:31 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: remove already solved TODOs + +2013-07-24 09:51:05 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-pad-monitor.h: + pad-monitor: accept unexpected flow return if pad is eos + Track eos event and mark that pad as eos so that checking for the + flow return knows when 'unexpected' is acceptable + +2013-07-23 15:18:51 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: improve caps proxying check on getcaps + Only check if fields are proxied for sink getcaps as it is when + downstream restrictions should be proxied. Also improve the + fields comparison to handle single value x multi value + (list/array/range) contain relations. + +2013-07-23 15:10:33 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: improve type conformance checking for caps + Replace the macro with a more powerful variadic function that can + check for more acceptable types for the same caps. + This removes a few more false positives + +2013-07-23 12:52:22 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: only expect a new segment if pad is running on push mode + For pull mode, it should just provide the buffers, regardless of getting + a new segment or not + +2013-07-23 12:14:26 -0300 Edward Hervey + + * validate/gst/qa/gst-qa.c: + gst-qa: show help and exit when no arguments are provided + Instead of attempting to create empty pipelines and weird things + happening :) + +2013-07-23 12:11:08 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: fix flushes checking + flush events shouldn't fail, so we don't need to rollback when it + returns false from downstream (this is common when downstream is still + not-linked) and it would cause gst-qa to spit false positives. + Also refactor the common event handling for both sink and src event + functions into a common place. Currently we handle flushes the same + for both pad's directions + +2013-07-23 11:51:07 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: only merge caps if they exist + Downtream can not be linked, so we shouldn't try to merge + NULL caps + +2013-07-22 20:50:02 -0300 Thiago Santos + + * validate/configure.ac: + configure: add nano version to enable Werror + +2013-07-22 20:09:35 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: fix expected setcaps fields comparison + Use the correct structure when getting the GValues and print different + messages for missing and different fields on the setcaps caps + +2013-07-22 20:09:07 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: improve timestamp ranges comparison message a little + Show the buffer range that is being compared. + +2013-07-22 15:05:04 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-pad-monitor.h: + pad-monitor: add check for serialized events order + Store expected serialized events and their 'timestamps' to check if + they are pushed in the same order/time as they were received + +2013-07-22 09:50:23 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-pad-monitor.h: + pad-monitor: add check for setcaps passing audio/video fields + Checks that the common audio/video fields are correctly passed + downstream after a setcaps + +2013-07-19 16:52:45 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-report.h: + pad-monitor: add check for getcaps proxying audio/video fields + Checks that the common audio/video fields are correctly proxied by + the elements after a getcaps + +2013-07-19 16:52:11 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-element-monitor.c: + * validate/gst/qa/gst-qa-element-monitor.h: + element-monitor: add is_encoder flag + Easy access to knowing if the monitored element is an encoder + +2013-07-18 16:53:46 -0400 Thibault Saunier + + * validate/configure.ac: + * validate/gst/qa/Makefile.am: + * validate/gst/qa/gst-qa-transcoding.c: + qa-transcoding: Add a binary program to easily test transcoding + +2013-07-18 18:20:09 -0400 Thibault Saunier + + * validate/gst/qa/gst-qa-bin-monitor.c: + * validate/gst/qa/gst-qa-element-monitor.c: + * validate/gst/qa/gst-qa-pad-monitor.c: + monitor(s): Avoid trying to disconnect handlers on instances that do not exist anymore + +2013-07-18 17:49:44 -0400 Thibault Saunier + + * validate/gst/qa/gst-qa-monitor.c: + * validate/gst/qa/gst-qa-monitor.h: + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-report.c: + * validate/gst/qa/gst-qa-report.h: + qa-report: Avoid reporting tons of times the exact same issue to users + Some of the issue can be reported once and for all. We are here avoiding to flood the + user with the same information repeated infinitely. + +2013-07-18 13:59:11 -0400 Thibault Saunier + + * validate/gst/qa/gst-qa-pad-monitor.c: + qa-pad-monitor: Do not use gst_private.h + +2013-07-19 11:14:39 -0300 Thiago Santos + + * validate/docs/qa-design.txt: + * validate/docs/qa-usage.txt: + docs: add design and usage docs + +2013-07-19 09:57:07 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-pad-monitor.h: + pad-monitor: improve timestamp ranges check + Keep the full range stored by the element in the monitor and check + if outgoing timestamps are within that range. It is simple and + should generally work. + +2013-07-18 14:49:23 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: fix combined flow checks + We can only check if we found a downstream monitor + +2013-07-18 14:49:01 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: fix caps field type checks + The type is GstValueList and not GArray + +2013-07-18 14:48:46 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: improve report messages with arguments + +2013-07-18 12:11:00 -0400 Thibault Saunier + + * validate/gst/qa/gst-qa-monitor.c: + * validate/gst/qa/gst-qa-report.c: + * validate/gst/qa/gst-qa-report.h: + qa-report: Pass the whole monitor when creating a report + So we have the proper source name already avalaible and in the future + we might need some more informations about the monitor itself. + +2013-07-18 12:00:29 -0400 Thibault Saunier + + * validate/gst/qa/gst-qa-pad-monitor.c: + qa-pad-monitor: Properly set target_name with as much info as possible + +2013-07-18 11:49:54 -0400 Thibault Saunier + + * validate/gst/qa/gst-qa-monitor.c: + * validate/gst/qa/gst-qa-monitor.h: + qa-monitor: Add a target name field that can be used even when the target is freed + +2013-07-18 11:49:25 -0400 Thibault Saunier + + * validate/gst/qa/gst-qa-monitor.c: + qa-monitor: Make the reference to the target a weak reference + +2013-07-18 12:09:13 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-report.c: + * validate/gst/qa/gst-qa-report.h: + qa-report: add debug flags for criticals + Allows the user to enable program abort if a report + is created with a certain level. + Use: + GST_QA=fatal_criticals,fatal_warnings,fatal_issues + +2013-07-18 10:59:11 -0400 Thibault Saunier + + * validate/gst/qa/gst-qa-report.c: + * validate/gst/qa/gst-qa-report.h: + qa-report: Do not keep a ref to the source but keep its name instead + We currently do not need to access the object source after its creation + but we need to be able to have a usefull for debugging name. + +2013-07-17 20:21:53 -0400 Thibault Saunier + + * validate/gst/qa/gst-qa-runner.c: + qa-runner: Add a 'report-added' signal + So it is possible to plug into the runner to get information about + what is going from outside of it. + +2013-07-17 19:56:52 -0400 Thibault Saunier + + * validate/gst/qa/gst-qa-monitor.c: + * validate/gst/qa/gst-qa-report.c: + * validate/gst/qa/gst-qa-report.h: + * validate/gst/qa/gst-qa-runner.c: + qa-report: Make it a boxed type + And make it refcounted, in 1.0 it should become a GstMiniObject, for + now, it is enough that way. + The goal is to be able to use it in signals + +2013-07-17 19:18:49 -0400 Thibault Saunier + + * validate/gst/qa/gst-qa-monitor.c: + * validate/gst/qa/gst-qa-monitor.h: + qa-monitor: Allow detaill message to be in printf format + So we can give proper informations about what is wrong to users + +2013-07-17 21:46:37 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-monitor-preload.c: + qa-monitor-preload: fix preload to work with pipeline creation + Wrap around the main gstreamer pipeline creation functions as wrapping + g_object_new requires rebuilding glib. + +2013-07-17 20:40:50 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: fix copy n paste mistake + Do not use GstFlowReturn where a boolean is expected + +2013-07-17 20:40:38 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: fix initialization of timestamp ranges + +2013-07-17 17:57:39 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-pad-monitor.h: + pad-monitor: check that returns are combined properly + When getting a return from a sink pad, check that it combines properly + the current returns from downstream source pads + +2013-07-17 14:36:44 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-pad-monitor.h: + pad-monitor: verify that pushed segment matches what was received + Check that src pads push segments that are compatible with what + was received on the sink pads + +2013-07-17 11:31:38 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-pad-monitor.h: + pad-monitor: output timestamps should be in range of received ones + Checks if the timestamps of pushed buffers are in the range of the + received buffer timestamps; + +2013-07-17 00:33:42 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: add check for out of segment buffers + +2013-07-17 00:30:21 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-pad-monitor.h: + pad-monitor: track current buffer timestamp and duration + This can be used to make sure outgoing buffers match the input + timestamps + +2013-07-17 00:29:38 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: check for when a flush start isn't expected + Complain when an unexpected flush-start is received + +2013-07-17 00:29:04 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-report.h: + pad-monitor: add checks for raw caps completeness + Check audio and video raw caps returned from getcaps for expected + fields and types + +2013-07-17 00:25:11 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-monitor.h: + qa-monitor: Fix typo in printf format for report debug messages + Stringify the arguments correctly for printing + +2013-07-16 23:19:13 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-pad-monitor.h: + pad-monitor: first buffer checks + Check that a newsegment is received before the first buffer and that + the first buffer running time is 0 + +2013-07-16 21:15:09 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-monitor.c: + * validate/gst/qa/gst-qa-monitor.h: + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-report.c: + * validate/gst/qa/gst-qa-report.h: + * validate/gst/qa/gst-qa-runner.c: + * validate/gst/qa/gst-qa-runner.h: + * validate/gst/qa/gst-qa.c: + qa-report: rework qa-report API + Remove error from GstQaErrorReport, making it only GstQaReport. Add + a level and use area and subarea code, with an extra string for message + adding details. + Provide macros on qa-monitor to make it easy to create reports. + +2013-07-16 09:17:44 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-pad-monitor.h: + pad-monitor: add stubs for getcaps/setcaps function wrapping + +2013-07-16 08:06:27 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-element-monitor.h: + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: add check for out of segment buffer data + +2013-07-15 10:15:06 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-report.c: + * validate/gst/qa/gst-qa-report.h: + * validate/gst/qa/gst-qa-runner.c: + qa-report: use gst_util_get_timestamp for report times + Makes it more aligned with GST_DEBUG output + +2013-07-15 09:27:34 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: split event checks for src and sink pads + Keeping those handlers separate should keep the code smaller and + easier to understand + +2013-07-12 16:02:25 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-bin-monitor.c: + * validate/gst/qa/gst-qa-bin-monitor.h: + * validate/gst/qa/gst-qa-element-monitor.c: + * validate/gst/qa/gst-qa-element-monitor.h: + * validate/gst/qa/gst-qa-monitor-factory.c: + * validate/gst/qa/gst-qa-monitor-factory.h: + * validate/gst/qa/gst-qa-monitor.c: + * validate/gst/qa/gst-qa-monitor.h: + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-pad-monitor.h: + * validate/gst/qa/gst-qa-runner.c: + qa-monitor: add parent relation for monitors + This is useful because Pad monitors will have to ask the + parent element monitors for some element details for + doing checks + +2013-07-12 15:42:56 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-element-monitor.c: + * validate/gst/qa/gst-qa-element-monitor.h: + qa-element-monitor: check if the element is a decoder + This can be used on checks for timestamps being inside segment + +2013-07-12 14:18:22 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-pad-monitor.h: + pad-monitor: add probes for src pads + To be used for further monitoring events and buffers for + src pads + +2013-07-12 13:32:08 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-report.c: + * validate/gst/qa/gst-qa-report.h: + qa-report: add a timestamp to error reports + +2013-07-12 02:10:06 -0300 Thiago Santos + + * validate/gst/qa/Makefile.am: + * validate/gst/qa/gst-qa-monitor.c: + * validate/gst/qa/gst-qa-monitor.h: + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-report.c: + * validate/gst/qa/gst-qa-report.h: + * validate/gst/qa/gst-qa-runner.c: + * validate/gst/qa/gst-qa-runner.h: + * validate/gst/qa/gst-qa.c: + qa-report: adds qa-report for reporting errors to GstQaRunner + The errors are printed directly to stdout and are accumulated at + GstQaRunner for being printed at the end if requested + +2013-07-12 01:23:48 -0300 Thiago Santos + + * validate/gst/qa/Makefile.am: + * validate/gst/qa/gst-qa-monitor-preload.c: + qa-monitor-preload: add functions to allow ld-preload to wrap pipelines + The preload functions wrap functions that can create pipelines and + attaches a runner to them for monitoring + +2013-07-12 00:41:43 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-bin-monitor.c: + * validate/gst/qa/gst-qa-bin-monitor.h: + * validate/gst/qa/gst-qa-element-monitor.c: + * validate/gst/qa/gst-qa-element-monitor.h: + * validate/gst/qa/gst-qa-monitor-factory.c: + * validate/gst/qa/gst-qa-monitor-factory.h: + * validate/gst/qa/gst-qa-monitor.c: + * validate/gst/qa/gst-qa-monitor.h: + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-pad-monitor.h: + * validate/gst/qa/gst-qa-runner.c: + * validate/gst/qa/gst-qa-runner.h: + qa-monitor: add runner property + runner stores the GstQaRunner that will receive the error reports + from the monitors + +2013-07-11 13:43:52 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-pad-monitor.h: + pad-monitor: make it able to initialize a segment + Do not take the initial format set to TIME too seriously when we + haven't got any newsegment event yet. If it is the first segment + received, switch our internal segment tracker to the event format + +2013-07-11 13:41:25 -0300 Thiago Santos + + * validate/gst/qa/gst-qa.c: + gst-qa: add seek-tests option + The seek-tests does a simple seeking after the pipeline has started + so that seeking checks can be performed by the monitors + +2013-07-11 02:07:41 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-pad-monitor.h: + pad-monitor: track some events + Segments, upstream seeks and flushes. Adding the following checks: + * A flush stop is expected after a flush start + * After a seek, the flushes/segment seqnum should be the same as the seek + +2013-07-11 00:05:17 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + pad-monitor: only set pad functions if they exist on the pad + Some functions should only be set on pads if they were originally + set, like the GetRange, Chain and BufferAlloc + +2013-07-11 00:04:41 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-bin-monitor.c: + * validate/gst/qa/gst-qa-element-monitor.c: + qa-bin-monitor/element-monitor: implement pad/element wrapping + Add code that creates new monitors when elements/pads are found + in bin and element monitors + +2013-07-11 00:03:54 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-bin-monitor.c: + * validate/gst/qa/gst-qa-element-monitor.c: + * validate/gst/qa/gst-qa-monitor.c: + * validate/gst/qa/gst-qa-pad-monitor.c: + qa-monitor: fix various start up issues + Fix reference count for monitored object, passing of constructor + parameter and base monitor property flag + +2013-07-10 18:38:09 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-pad-monitor.h: + pad-monitor: instrument to monitor buffer/event/query/alloc flows + Replace pad functions with monitor functions that can do pre/post + checks and call the original functions + +2013-07-10 14:03:49 -0300 Thiago Santos + + * validate/gst/qa/Makefile.am: + * validate/gst/qa/gst-qa-bin-monitor.c: + * validate/gst/qa/gst-qa-bin-monitor.h: + * validate/gst/qa/gst-qa-element-monitor.c: + * validate/gst/qa/gst-qa-element-monitor.h: + * validate/gst/qa/gst-qa-monitor-factory.c: + * validate/gst/qa/gst-qa-monitor.c: + * validate/gst/qa/gst-qa-monitor.h: + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-pad-monitor.h: + qa-monitor: add base class for monitors + The base class adds a 'object' property to hold the monitored object, + it can only be set on construction. Also the constructor now + automatically calls the element set up + +2013-07-09 19:20:55 -0300 Thiago Santos + + * validate/gst/qa/Makefile.am: + * validate/gst/qa/gst-qa-bin-monitor.c: + * validate/gst/qa/gst-qa-bin-monitor.h: + * validate/gst/qa/gst-qa-element-monitor.c: + * validate/gst/qa/gst-qa-element-monitor.h: + * validate/gst/qa/gst-qa-monitor-factory.c: + qa-bin-monitor: adds a bin monitor + Extends element-monitor to also wrap child elements + +2013-07-09 17:38:47 -0300 Thiago Santos + + * validate/gst/qa/Makefile.am: + * validate/gst/qa/gst-qa-element-monitor.c: + * validate/gst/qa/gst-qa-element-monitor.h: + * validate/gst/qa/gst-qa-element-wrapper.c: + * validate/gst/qa/gst-qa-element-wrapper.h: + * validate/gst/qa/gst-qa-monitor-factory.c: + * validate/gst/qa/gst-qa-monitor-factory.h: + * validate/gst/qa/gst-qa-pad-monitor.c: + * validate/gst/qa/gst-qa-pad-monitor.h: + * validate/gst/qa/gst-qa-pad-wrapper.h: + * validate/gst/qa/gst-qa-runner.c: + * validate/gst/qa/gst-qa-runner.h: + qa: renaming Wrapper -> Monitor + +2013-07-09 16:52:02 -0300 Thiago Santos + + * validate/gst/qa/Makefile.am: + * validate/gst/qa/gst-qa-element-wrapper.c: + * validate/gst/qa/gst-qa-pad-wrapper.c: + * validate/gst/qa/gst-qa-pad-wrapper.h: + * validate/gst/qa/gst-qa-runner.c: + qa-pad-wrapper: adds stub class for pad QA wrapper + Also fixes _new functions to ref the elements intead of + ownership transfers + +2013-07-09 16:39:38 -0300 Thiago Santos + + * validate/gst/qa/gst-qa-element-wrapper.c: + * validate/gst/qa/gst-qa-element-wrapper.h: + qa-element-wrapper: add code for iterating and monitoring pads creation + This will be used to create the wrappers for pads + +2013-07-09 16:13:00 -0300 Thiago Santos + + * validate/gst/qa/.gitignore: + gitignore: ignore gst-qa binary + +2013-07-09 16:08:30 -0300 Thiago Santos + + * validate/AUTHORS: + * validate/COPYING: + * validate/ChangeLog: + * validate/Makefile.am: + * validate/NEWS: + * validate/README: + * validate/autogen.sh: + * validate/configure.ac: + * validate/gst/Makefile.am: + * validate/gst/qa/Makefile.am: + * validate/gst/qa/gst-qa-element-wrapper.c: + * validate/gst/qa/gst-qa-element-wrapper.h: + * validate/gst/qa/gst-qa-runner.c: + * validate/gst/qa/gst-qa-runner.h: + * validate/gst/qa/gst-qa-wrapper-factory.c: + * validate/gst/qa/gst-qa-wrapper-factory.h: + * validate/gst/qa/gst-qa.c: + * validate/gst/qa/qa.h: + qa: adds gst-qa binary and basic classes to run the QA tests + The classes are mostly a stub for now, but the gst-qa already + has a minimum to start them; + +2013-07-09 16:07:58 -0300 Thiago Santos + + * common: + * validate/.gitmodules: + qa: add common submodule + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000000..0e581c39b8 --- /dev/null +++ b/NEWS @@ -0,0 +1,299 @@ +GStreamer 1.20 Release Notes + +GStreamer 1.20 has not been released yet. It is scheduled for release +around October/November 2021. + +1.19.x is the unstable development version that is being developed in +the git main branch and which will eventually result in 1.20, and 1.19.2 +is the current development release in that series + +It is expected that feature freeze will be in early October 2021, +followed by one or two 1.19.9x pre-releases and the new 1.20 stable +release around October/November 2021. + +1.20 will be backwards-compatible to the stable 1.18, 1.16, 1.14, 1.12, +1.10, 1.8, 1.6,, 1.4, 1.2 and 1.0 release series. + +See https://gstreamer.freedesktop.org/releases/1.20/ for the latest +version of this document. + +Last updated: Wednesday 22 September 2021, 18:00 UTC (log) + +Introduction + +The GStreamer team is proud to announce a new major feature release in +the stable 1.x API series of your favourite cross-platform multimedia +framework! + +As always, this release is again packed with many new features, bug +fixes and other improvements. + +Highlights + +- this section will be completed in due course + +Major new features and changes + +Noteworthy new features and API + +- this section will be filled in in due course + +New elements + +- this section will be filled in in due course + +New element features and additions + +- this section will be filled in in due course + +Plugin and library moves + +- this section will be filled in in due course + +- There were no plugin moves or library moves in this cycle. + +Plugin removals + +The following elements or plugins have been removed: + +- this section will be filled in in due course + +Miscellaneous API additions + +- this section will be filled in in due course + +Miscellaneous performance, latency and memory optimisations + +- this section will be filled in in due course + +Miscellaneous other changes and enhancements + +- this section will be filled in in due course + +Tracing framework and debugging improvements + +- this section will be filled in in due course + +Tools + +- this section will be filled in in due course + +GStreamer RTSP server + +- this section will be filled in in due course + +GStreamer VAAPI + +- this section will be filled in in due course + +GStreamer OMX + +- this section will be filled in in due course + +GStreamer Editing Services and NLE + +- this section will be filled in in due course + +GStreamer validate + +- this section will be filled in in due course + +GStreamer Python Bindings + +- this section will be filled in in due course + +GStreamer C# Bindings + +- this section will be filled in in due course + +GStreamer Rust Bindings and Rust Plugins + +The GStreamer Rust bindings are released separately with a different +release cadence that’s tied to gtk-rs, but the latest release has +already been updated for the upcoming new GStreamer 1.20 API. + +gst-plugins-rs, the module containing GStreamer plugins written in Rust, +has also seen lots of activity with many new elements and plugins. + +What follows is a list of elements and plugins available in +gst-plugins-rs, so people don’t miss out on all those potentially useful +elements that have no C equivalent. + +- FIXME: add new elements + +Rust audio plugins + +- audiornnoise: New element for audio denoising which implements the + noise removal algorithm of the Xiph RNNoise library, in Rust +- rsaudioecho: Port of the audioecho element from gst-plugins-good + rsaudioloudnorm: Live audio loudness normalization element based on + the FFmpeg af_loudnorm filter +- claxondec: FLAC lossless audio codec decoder element based on the + pure-Rust claxon implementation +- csoundfilter: Audio filter that can use any filter defined via the + Csound audio programming language +- lewtondec: Vorbis audio decoder element based on the pure-Rust + lewton implementation + +Rust video plugins + +- cdgdec/cdgparse: Decoder and parser for the CD+G video codec based + on a pure-Rust CD+G implementation, used for example by karaoke CDs +- cea608overlay: CEA-608 Closed Captions overlay element +- cea608tott: CEA-608 Closed Captions to timed-text (e.g. VTT or SRT + subtitles) converter +- tttocea608: CEA-608 Closed Captions from timed-text converter +- mccenc/mccparse: MacCaption Closed Caption format encoder and parser +- sccenc/sccparse: Scenarist Closed Caption format encoder and parser +- dav1dec: AV1 video decoder based on the dav1d decoder implementation + by the VLC project +- rav1enc: AV1 video encoder based on the fast and pure-Rust rav1e + encoder implementation +- rsflvdemux: Alternative to the flvdemux FLV demuxer element from + gst-plugins-good, not feature-equivalent yet +- rsgifenc/rspngenc: GIF/PNG encoder elements based on the pure-Rust + implementations by the image-rs project + +Rust text plugins + +- textwrap: Element for line-wrapping timed text (e.g. subtitles) for + better screen-fitting, including hyphenation support for some + languages + +Rust network plugins + +- reqwesthttpsrc: HTTP(S) source element based on the Rust + reqwest/hyper HTTP implementations and almost feature-equivalent + with the main GStreamer HTTP source souphttpsrc +- s3src/s3sink: Source/sink element for the Amazon S3 cloud storage +- awstranscriber: Live audio to timed text transcription element using + the Amazon AWS Transcribe API + +Generic Rust plugins + +- sodiumencrypter/sodiumdecrypter: Encryption/decryption element based + on libsodium/NaCl +- togglerecord: Recording element that allows to pause/resume + recordings easily and considers keyframe boundaries +- fallbackswitch/fallbacksrc: Elements for handling potentially + failing (network) sources, restarting them on errors/timeout and + showing a fallback stream instead +- threadshare: Set of elements that provide alternatives for various + existing GStreamer elements but allow to share the streaming threads + between each other to reduce the number of threads +- rsfilesrc/rsfilesink: File source/sink elements as replacements for + the existing filesrc/filesink elements + +Build and Dependencies + +- this section will be filled in in due course + +gst-build + +- this section will be filled in in due course + +Cerbero + +Cerbero is a meta build system used to build GStreamer plus dependencies +on platforms where dependencies are not readily available, such as +Windows, Android, iOS and macOS. + +General improvements + +- this section will be filled in in due course + +macOS / iOS + +- this section will be filled in in due course + +Windows + +- this section will be filled in in due course + +Windows MSI installer + +- this section will be filled in in due course + +Linux + +- this section will be filled in in due course + +Android + +- this section will be filled in in due course + +Platform-specific changes and improvements + +Android + +- this section will be filled in in due course + +macOS and iOS + +- this section will be filled in in due course + +Windows + +- this section will be filled in in due course + +Linux + +- this section will be filled in in due course + +Documentation improvements + +- this section will be filled in in due course + +Possibly Breaking Changes + +- this section will be filled in in due course +- MPEG-TS SCTE-35 API changes (FIXME: flesh out) +- gst_parse_launch() and friends now error out on non-existing + properties on top-level bins where they would silently fail and + ignore those before. + +Known Issues + +- this section will be filled in in due course + +- There are a couple of known WebRTC-related regressions/blockers: + + - webrtc: DTLS setup with Chrome is broken + - webrtcbin: First keyframe is usually lost + +Contributors + +- this section will be filled in in due course + +… and many others who have contributed bug reports, translations, sent +suggestions or helped testing. + +Stable 1.20 branch + +After the 1.20.0 release there will be several 1.20.x bug-fix releases +which will contain bug fixes which have been deemed suitable for a +stable branch, but no new features or intrusive changes will be added to +a bug-fix release usually. The 1.20.x bug-fix releases will be made from +the git 1.20 branch, which will be a stable branch. + +1.20.0 + +1.20.0 is scheduled to be released around October/November 2021. + +Schedule for 1.22 + +Our next major feature release will be 1.22, and 1.21 will be the +unstable development version leading up to the stable 1.22 release. The +development of 1.21/1.22 will happen in the git main branch. + +The plan for the 1.22 development cycle is yet to be confirmed. + +1.22 will be backwards-compatible to the stable 1.20, 1.18, 1.16, 1.14, +1.12, 1.10, 1.8, 1.6, 1.4, 1.2 and 1.0 release series. + +------------------------------------------------------------------------ + +These release notes have been prepared by Tim-Philipp Müller with +contributions from … + +License: CC BY-SA 4.0 diff --git a/RELEASE b/RELEASE new file mode 100644 index 0000000000..17bcd1b8f1 --- /dev/null +++ b/RELEASE @@ -0,0 +1,96 @@ +This is GStreamer gst-devtools 1.19.2. + +GStreamer 1.19 is the development branch leading up to the next major +stable version which will be 1.20. + +The 1.19 development series adds new features on top of the 1.18 series and is +part of the API and ABI-stable 1.x release series of the GStreamer multimedia +framework. + +Full release notes will one day be found at: + + https://gstreamer.freedesktop.org/releases/1.20/ + +Binaries for Android, iOS, Mac OS X and Windows will usually be provided +shortly after the release. + +This module will not be very useful by itself and should be used in conjunction +with other GStreamer modules for a complete multimedia experience. + + - gstreamer: provides the core GStreamer libraries and some generic plugins + + - gst-plugins-base: a basic set of well-supported plugins and additional + media-specific GStreamer helper libraries for audio, + video, rtsp, rtp, tags, OpenGL, etc. + + - gst-plugins-good: a set of well-supported plugins under our preferred + license + + - gst-plugins-ugly: a set of well-supported plugins which might pose + problems for distributors + + - gst-plugins-bad: a set of plugins of varying quality that have not made + their way into one of core/base/good/ugly yet, for one + reason or another. Many of these are are production quality + elements, but may still be missing documentation or unit + tests; others haven't passed the rigorous quality testing + we expect yet. + + - gst-libav: a set of codecs plugins based on the ffmpeg library. This is + where you can find audio and video decoders and encoders + for a wide variety of formats including H.264, AAC, etc. + + - gstreamer-vaapi: hardware-accelerated video decoding and encoding using + VA-API on Linux. Primarily for Intel graphics hardware. + + - gst-omx: hardware-accelerated video decoding and encoding, primarily for + embedded Linux systems that provide an OpenMax + implementation layer such as the Raspberry Pi. + + - gst-rtsp-server: library to serve files or streaming pipelines via RTSP + + - gst-editing-services: library an plugins for non-linear editing + +==== Download ==== + +You can find source releases of gstreamer in the download +directory: https://gstreamer.freedesktop.org/src/gstreamer/ + +The git repository and details how to clone it can be found at +https://gitlab.freedesktop.org/gstreamer/ + +==== Homepage ==== + +The project's website is https://gstreamer.freedesktop.org/ + +==== Support and Bugs ==== + +We have recently moved from GNOME Bugzilla to GitLab on freedesktop.org +for bug reports and feature requests: + + https://gitlab.freedesktop.org/gstreamer + +Please submit patches via GitLab as well, in form of Merge Requests. See + + https://gstreamer.freedesktop.org/documentation/contribute/ + +for more details. + +For help and support, please subscribe to and send questions to the +gstreamer-devel mailing list (see below for details). + +There is also a #gstreamer IRC channel on the Freenode IRC network. + +==== Developers ==== + +GStreamer source code repositories can be found on GitLab on freedesktop.org: + + https://gitlab.freedesktop.org/gstreamer + +and can also be cloned from there and this is also where you can submit +Merge Requests or file issues for bugs or feature requests. + +Interested developers of the core library, plugins, and applications should +subscribe to the gstreamer-devel list: + + https://lists.freedesktop.org/mailman/listinfo/gstreamer-devel diff --git a/debug-viewer/.gitignore b/debug-viewer/.gitignore new file mode 100644 index 0000000000..e44fa3d75e --- /dev/null +++ b/debug-viewer/.gitignore @@ -0,0 +1,14 @@ + +*.pyc +*.pyo + +*.glade.bak +*.gladep +*.gladep.bak + +/build +/dist +/MANIFEST + +po/*.pot +po/mo diff --git a/debug-viewer/GstDebugViewer/Common/Data.py b/debug-viewer/GstDebugViewer/Common/Data.py new file mode 100644 index 0000000000..d743a47208 --- /dev/null +++ b/debug-viewer/GstDebugViewer/Common/Data.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Development Utilities +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer Development Utilities Common Data module.""" + +import gi + +from gi.repository import GObject + + +class Dispatcher (object): + + def __call__(self, iterator): + + raise NotImplementedError("derived classes must override this method") + + def cancel(self): + + pass + + +class DefaultDispatcher (Dispatcher): + + def __call__(self, iterator): + + for x in iterator: + pass + + +class GSourceDispatcher (Dispatcher): + + def __init__(self): + + Dispatcher.__init__(self) + + self.source_id = None + + def __call__(self, iterator): + + if self.source_id is not None: + GObject.source_remove(self.source_id) + + def iteration(): + r = iterator.__next__() + if not r: + self.source_id = None + return r + + self.source_id = GObject.idle_add( + iteration, priority=GObject.PRIORITY_LOW) + + def cancel(self): + + if self.source_id is None: + return + + GObject.source_remove(self.source_id) + self.source_id = None diff --git a/debug-viewer/GstDebugViewer/Common/GUI.py b/debug-viewer/GstDebugViewer/Common/GUI.py new file mode 100644 index 0000000000..13b3ad3d63 --- /dev/null +++ b/debug-viewer/GstDebugViewer/Common/GUI.py @@ -0,0 +1,528 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Development Utilities +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer Development Utilities Common GUI module.""" + +import os + +import logging + +import gi + +gi.require_version('Gtk', '3.0') +from gi.repository import GObject +from gi.repository import Gtk +from gi.repository import Gdk +from gi.types import GObjectMeta + +import GstDebugViewer +from GstDebugViewer.Common import utils +from .generictreemodel import GenericTreeModel + + +def widget_add_popup_menu(widget, menu, button=3): + + def popup_callback(widget, event): + + if event.button == button: + menu.popup( + None, None, None, None, event.button, event.get_time()) + return False + + widget.connect("button-press-event", popup_callback) + + +class Actions (dict): + + def __init__(self): + + dict.__init__(self) + + self.groups = {} + + def __getattr__(self, name): + + try: + return self[name] + except KeyError: + if "_" in name: + try: + return self[name.replace("_", "-")] + except KeyError: + pass + + raise AttributeError("no action with name %r" % (name,)) + + def add_group(self, group): + + name = group.props.name + if name in self.groups: + raise ValueError("already have a group named %s", name) + self.groups[name] = group + for action in group.list_actions(): + self[action.props.name] = action + + +class Widgets (dict): + + def __init__(self, builder): + + widgets = (obj for obj in builder.get_objects() + if isinstance(obj, Gtk.Buildable)) + # Gtk.Widget.get_name() shadows out the GtkBuildable interface method + # of the same name, hence calling the unbound interface method here: + items = ((Gtk.Buildable.get_name(w), w,) for w in widgets) + + dict.__init__(self, items) + + def __getattr__(self, name): + + try: + return self[name] + except KeyError: + if "_" in name: + try: + return self[name.replace("_", "-")] + except KeyError: + pass + + raise AttributeError("no widget with name %r" % (name,)) + + +class WidgetFactory (object): + + def __init__(self, directory): + + self.directory = directory + + def get_builder(self, filename): + + builder_filename = os.path.join(self.directory, filename) + + builder = Gtk.Builder() + builder.set_translation_domain(GstDebugViewer.GETTEXT_DOMAIN) + builder.add_from_file(builder_filename) + + return builder + + def make(self, filename, widget_name, autoconnect=None): + + builder = self.get_builder(filename) + + if autoconnect is not None: + builder.connect_signals(autoconnect) + + return Widgets(builder) + + def make_one(self, filename, widget_name): + + builder = self.get_builder(filename) + + return builder.get_object(widget_name) + + +class UIFactory (object): + + def __init__(self, ui_filename, actions=None): + + self.filename = ui_filename + if actions: + self.action_groups = actions.groups + else: + self.action_groups = () + + def make(self, extra_actions=None): + + ui_manager = Gtk.UIManager() + for action_group in list(self.action_groups.values()): + ui_manager.insert_action_group(action_group, 0) + if extra_actions: + for action_group in extra_actions.groups: + ui_manager.insert_action_group(action_group, 0) + ui_manager.add_ui_from_file(self.filename) + ui_manager.ensure_update() + + return ui_manager + + +class MetaModel (GObjectMeta): + + """Meta class for easy setup of gtk tree models. + + Looks for a class attribute named `columns' which must be set to a + sequence of the form name1, type1, name2, type2, ..., where the + names are strings. This metaclass adds the following attributes + to created classes: + + cls.column_types = (type1, type2, ...) + cls.column_ids = (0, 1, ...) + cls.name1 = 0 + cls.name2 = 1 + ... + + Example: A Gtk.ListStore derived model can use + + columns = ("COL_NAME", str, "COL_VALUE", str) + + and use this in __init__: + + GObject.GObject.__init__ (self, *self.column_types) + + Then insert data like this: + + self.set (self.append (), + self.COL_NAME, "spam", + self.COL_VALUE, "ham") + """ + + def __init__(cls, name, bases, dict): + + super(MetaModel, cls).__init__(name, bases, dict) + + spec = tuple(cls.columns) + + column_names = spec[::2] + column_types = spec[1::2] + column_indices = list(range(len(column_names))) + + for col_index, col_name, in zip(column_indices, column_names): + setattr(cls, col_name, col_index) + + cls.column_types = column_types + cls.column_ids = tuple(column_indices) + + +class Manager (object): + + """GUI Manager base class.""" + + @classmethod + def iter_item_classes(cls): + + msg = "%s class does not support manager item class access" + raise NotImplementedError(msg % (cls.__name__,)) + + @classmethod + def find_item_class(self, **kw): + + return self.__find_by_attrs(self.iter_item_classes(), kw) + + def iter_items(self): + + msg = "%s object does not support manager item access" + raise NotImplementedError(msg % (type(self).__name__,)) + + def find_item(self, **kw): + + return self.__find_by_attrs(self.iter_items(), kw) + + @staticmethod + def __find_by_attrs(i, kw): + + from operator import attrgetter + + if len(kw) != 1: + raise ValueError("need exactly one keyword argument") + + attr, value = list(kw.items())[0] + getter = attrgetter(attr) + + for item in i: + if getter(item) == value: + return item + else: + raise KeyError("no item such that item.%s == %r" % (attr, value,)) + + +class StateString (object): + + """Descriptor for binding to StateSection classes.""" + + def __init__(self, option, default=None): + + self.option = option + self.default = default + + def __get__(self, section, section_class=None): + + import configparser + + if section is None: + return self + + try: + return self.get(section) + except (configparser.NoSectionError, + configparser.NoOptionError,): + return self.get_default(section) + + def __set__(self, section, value): + + import configparser + + self.set(section, value) + + def get(self, section): + + return section.get(self) + + def get_default(self, section): + + return self.default + + def set(self, section, value): + + if value is None: + value = "" + + section.set(self, str(value)) + + +class StateBool (StateString): + + """Descriptor for binding to StateSection classes.""" + + def get(self, section): + + return section.state._parser.getboolean(section._name, self.option) + + +class StateInt (StateString): + + """Descriptor for binding to StateSection classes.""" + + def get(self, section): + + return section.state._parser.getint(section._name, self.option) + + +class StateInt4 (StateString): + + """Descriptor for binding to StateSection classes. This implements storing + a tuple of 4 integers.""" + + def get(self, section): + + value = StateString.get(self, section) + + try: + l = value.split(",") + if len(l) != 4: + return None + else: + return tuple((int(v) for v in l)) + except (AttributeError, TypeError, ValueError,): + return None + + def set(self, section, value): + + if value is None: + svalue = "" + elif len(value) != 4: + raise ValueError("value needs to be a 4-sequence, or None") + else: + svalue = ", ".join((str(v) for v in value)) + + return StateString.set(self, section, svalue) + + +class StateItem (StateString): + + """Descriptor for binding to StateSection classes. This implements storing + a class controlled by a Manager class.""" + + def __init__(self, option, manager_class, default=None): + + StateString.__init__(self, option, default=default) + + self.manager = manager_class + + def get(self, section): + + value = SectionString.get(self, section) + + if not value: + return None + + return self.parse_item(value) + + def set(self, section, value): + + if value is None: + svalue = "" + else: + svalue = value.name + + StateString.set(self, section, svalue) + + def parse_item(self, value): + + name = value.strip() + + try: + return self.manager.find_item_class(name=name) + except KeyError: + return None + + +class StateItemList (StateItem): + + """Descriptor for binding to StateSection classes. This implements storing + an ordered set of Manager items.""" + + def get(self, section): + + value = StateString.get(self, section) + + if not value: + return [] + + classes = [] + for name in value.split(","): + item_class = self.parse_item(name) + if item_class is None: + continue + if not item_class in classes: + classes.append(item_class) + + return classes + + def get_default(self, section): + + default = StateItem.get_default(self, section) + if default is None: + return [] + else: + return default + + def set(self, section, value): + + if value is None: + svalue = "" + else: + svalue = ", ".join((v.name for v in value)) + + StateString.set(self, section, svalue) + + +class StateSection (object): + + _name = None + + def __init__(self, state): + + self.state = state + + if self._name is None: + raise NotImplementedError( + "subclasses must override the _name attribute") + + def get(self, state_string): + + return self.state._parser.get(self._name, state_string.option) + + def set(self, state_string, value): + + import configparser + + parser = self.state._parser + + try: + parser.set(self._name, state_string.option, value) + except configparser.NoSectionError: + parser.add_section(self._name) + parser.set(self._name, state_string.option, value) + + +class State (object): + + def __init__(self, filename, old_filenames=()): + + import configparser + + self.sections = {} + + self._filename = filename + self._parser = configparser.RawConfigParser() + success = self._parser.read([filename]) + if not success: + for old_filename in old_filenames: + success = self._parser.read([old_filename]) + if success: + break + + def add_section_class(self, section_class): + + self.sections[section_class._name] = section_class(self) + + def save(self): + + with utils.SaveWriteFile(self._filename, "wt") as fp: + self._parser.write(fp) + + +class WindowState (object): + + def __init__(self): + + self.logger = logging.getLogger("ui.window-state") + + self.is_maximized = False + + def attach(self, window, state): + + self.window = window + self.state = state + + self.window.connect("window-state-event", + self.handle_window_state_event) + + geometry = self.state.geometry + if geometry: + self.window.move(*geometry[:2]) + self.window.set_default_size(*geometry[2:]) + + if self.state.maximized: + self.logger.debug("initially maximized") + self.window.maximize() + + def detach(self): + + window = self.window + + self.state.maximized = self.is_maximized + if not self.is_maximized: + position = tuple(window.get_position()) + size = tuple(window.get_size()) + self.state.geometry = position + size + + self.window.disconnect_by_func(self.handle_window_state_event) + self.window = None + + def handle_window_state_event(self, window, event): + + if not event.changed_mask & Gdk.WindowState.MAXIMIZED: + return + + if event.new_window_state & Gdk.WindowState.MAXIMIZED: + self.logger.debug("maximized") + self.is_maximized = True + else: + self.logger.debug("unmaximized") + self.is_maximized = False diff --git a/debug-viewer/GstDebugViewer/Common/Main.py b/debug-viewer/GstDebugViewer/Common/Main.py new file mode 100644 index 0000000000..3d6c6f780a --- /dev/null +++ b/debug-viewer/GstDebugViewer/Common/Main.py @@ -0,0 +1,366 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Development Utilities +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer Development Utilities Common Main module.""" + +import sys +import os +import traceback +from operator import attrgetter +import logging +import locale +import gettext +from gettext import gettext as _, ngettext + +import gi + +from gi.repository import GLib +from gi.repository import GObject +from gi.repository import Gtk + + +class ExceptionHandler (object): + + exc_types = (Exception,) + priority = 50 + inherit_fork = True + + _handling_exception = False + + def __call__(self, exc_type, exc_value, exc_traceback): + + raise NotImplementedError( + "derived classes need to override this method") + + +class DefaultExceptionHandler (ExceptionHandler): + exc_types = (BaseException,) + priority = 0 + inherit_fork = True + + def __init__(self, excepthook): + + ExceptionHandler.__init__(self) + + self.excepthook = excepthook + + def __call__(self, *exc_info): + + return self.excepthook(*exc_info) + + +class ExitOnInterruptExceptionHandler (ExceptionHandler): + + exc_types = (KeyboardInterrupt,) + priority = 100 + inherit_fork = False + + exit_status = 2 + + def __call__(self, *args): + + print("Interrupt caught, exiting.", file=sys.stderr) + + sys.exit(self.exit_status) + + +class MainLoopWrapper (ExceptionHandler): + + priority = 95 + inherit_fork = False + + def __init__(self, enter, exit): + + ExceptionHandler.__init__(self) + + self.exc_info = (None,) * 3 + self.enter = enter + self.exit = exit + + def __call__(self, *exc_info): + + self.exc_info = exc_info + self.exit() + + def run(self): + + ExceptHookManager.register_handler(self) + try: + self.enter() + finally: + ExceptHookManager.unregister_handler(self) + + if self.exc_info != (None,) * 3: + # Re-raise unhandled exception that occured while running the loop. + exc_type, exc_value, exc_tb = self.exc_info + raise exc_value + + +class ExceptHookManagerClass (object): + + def __init__(self): + + self._in_forked_child = False + + self.handlers = [] + + def setup(self): + + if sys.excepthook == self.__excepthook: + raise ValueError("already set up") + + hook = sys.excepthook + self.__instrument_excepthook() + self.__instrument_fork() + self.register_handler(DefaultExceptionHandler(hook)) + + def shutdown(self): + + if sys.excepthook != self.__excepthook: + raise ValueError("not set up") + + self.__restore_excepthook() + self.__restore_fork() + + def __instrument_excepthook(self): + + hook = sys.excepthook + self._original_excepthook = hook + sys.excepthook = self.__excepthook + + def __restore_excepthook(self): + + sys.excepthook = self._original_excepthook + + def __instrument_fork(self): + + try: + fork = os.fork + except AttributeError: + # System has no fork() system call. + self._original_fork = None + else: + self._original_fork = fork + os.fork = self.__fork + + def __restore_fork(self): + + if not hasattr(os, "fork"): + return + + os.fork = self._original_fork + + def entered_forked_child(self): + + self._in_forked_child = True + + for handler in tuple(self.handlers): + if not handler.inherit_fork: + self.handlers.remove(handler) + + def register_handler(self, handler): + + if self._in_forked_child and not handler.inherit_fork: + return + + self.handlers.append(handler) + + def unregister_handler(self, handler): + + self.handlers.remove(handler) + + def __fork(self): + + pid = self._original_fork() + if pid == 0: + # Child process. + self.entered_forked_child() + return pid + + def __excepthook(self, exc_type, exc_value, exc_traceback): + + for handler in sorted(self.handlers, + key=attrgetter("priority"), + reverse=True): + + if handler._handling_exception: + continue + + for type_ in handler.exc_types: + if issubclass(exc_type, type_): + break + else: + continue + + handler._handling_exception = True + handler(exc_type, exc_value, exc_traceback) + # Not using try...finally on purpose here. If the handler itself + # fails with an exception, this prevents recursing into it again. + handler._handling_exception = False + return + + else: + from warnings import warn + warn("ExceptHookManager: unhandled %r" % (exc_value,), + RuntimeWarning, + stacklevel=2) + + +ExceptHookManager = ExceptHookManagerClass() + + +class PathsBase (object): + + data_dir = None + icon_dir = None + locale_dir = None + + @classmethod + def setup_installed(cls, data_prefix): + """Set up paths for running from a regular installation.""" + + pass + + @classmethod + def setup_uninstalled(cls, source_dir): + """Set up paths for running 'uninstalled' (i.e. directly from the + source dist).""" + + pass + + @classmethod + def ensure_setup(cls): + """If paths are still not set up, try to set from a fallback.""" + + if cls.data_dir is None: + source_dir = os.path.dirname( + os.path.dirname(os.path.abspath(__file__))) + cls.setup_uninstalled(source_dir) + + def __new__(cls): + + raise RuntimeError("do not create instances of this class -- " + "use the class object directly") + + +class PathsProgramBase (PathsBase): + + program_name = None + + @classmethod + def setup_installed(cls, data_prefix): + + if cls.program_name is None: + raise NotImplementedError( + "derived classes need to set program_name attribute") + + cls.data_dir = os.path.join(data_prefix, cls.program_name) + cls.icon_dir = os.path.join(data_prefix, "icons") + cls.locale_dir = os.path.join(data_prefix, "locale") + + @classmethod + def setup_uninstalled(cls, source_dir): + """Set up paths for running 'uninstalled' (i.e. directly from the + source dist).""" + + # This is essential: The GUI module needs to find the .glade file. + cls.data_dir = os.path.join(source_dir, "data") + + # The locale data might be missing if "setup.py build" wasn't run. + cls.locale_dir = os.path.join(source_dir, "build", "mo") + + # Not setting icon_dir. It is not useful since we don't employ the + # needed directory structure in the source dist. + + +def _init_excepthooks(): + + ExceptHookManager.setup() + ExceptHookManager.register_handler(ExitOnInterruptExceptionHandler()) + + +def _init_paths(paths): + + paths.ensure_setup() + + +def _init_locale(gettext_domain=None): + + if Paths.locale_dir and gettext_domain is not None: + try: + locale.setlocale(locale.LC_ALL, "") + except locale.Error as exc: + from warnings import warn + warn("locale error: %s" % (exc,), + RuntimeWarning, + stacklevel=2) + Paths.locale_dir = None + else: + gettext.bindtextdomain(gettext_domain, Paths.locale_dir) + gettext.textdomain(gettext_domain) + gettext.bind_textdomain_codeset(gettext_domain, "UTF-8") + + +def _init_logging(level): + if level == "none": + return + + mapping = {"debug": logging.DEBUG, + "info": logging.INFO, + "warning": logging.WARNING, + "error": logging.ERROR, + "critical": logging.CRITICAL} + logging.basicConfig(level=mapping[level], + format='%(asctime)s.%(msecs)03d %(levelname)8s %(name)20s: %(message)s', + datefmt='%H:%M:%S') + + logger = logging.getLogger("main") + logger.debug("logging at level %s", logging.getLevelName(level)) + logger.info("using Python %i.%i.%i %s %i", *sys.version_info) + + +def _init_log_option(parser): + choices = ["none", "debug", "info", "warning", "error", "critical"] + parser.add_option("--log-level", "-l", + type="choice", + choices=choices, + action="store", + dest="log_level", + default="none", + help=_("Enable logging, possible values: ") + ", ".join(choices)) + return parser + + +def main(main_function, option_parser, gettext_domain=None, paths=None): + + # FIXME: + global Paths + Paths = paths + + _init_excepthooks() + _init_paths(paths) + _init_locale(gettext_domain) + parser = _init_log_option(option_parser) + options, args = option_parser.parse_args() + _init_logging(options.log_level) + + try: + main_function(args) + finally: + logging.shutdown() diff --git a/debug-viewer/GstDebugViewer/Common/__init__.py b/debug-viewer/GstDebugViewer/Common/__init__.py new file mode 100644 index 0000000000..ad20a9321d --- /dev/null +++ b/debug-viewer/GstDebugViewer/Common/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Development Utilities +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer Development Utilities Common package.""" + +from . import Data +from . import GUI +from . import Main +from . import utils diff --git a/debug-viewer/GstDebugViewer/Common/generictreemodel.py b/debug-viewer/GstDebugViewer/Common/generictreemodel.py new file mode 100644 index 0000000000..4ceba4c294 --- /dev/null +++ b/debug-viewer/GstDebugViewer/Common/generictreemodel.py @@ -0,0 +1,420 @@ +# -*- Mode: Python; py-indent-offset: 4 -*- +# generictreemodel - GenericTreeModel implementation for pygtk compatibility. +# Copyright (C) 2013 Simon Feltman +# +# generictreemodel.py: GenericTreeModel implementation for pygtk compatibility +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, see . + + +# System +import sys +import random +import collections +import ctypes + +# GObject +from gi.repository import GObject +from gi.repository import Gtk + + +class _CTreeIter(ctypes.Structure): + _fields_ = [('stamp', ctypes.c_int), + ('user_data', ctypes.c_void_p), + ('user_data2', ctypes.c_void_p), + ('user_data3', ctypes.c_void_p)] + + @classmethod + def from_iter(cls, iter): + offset = sys.getsizeof(object()) # size of PyObject_HEAD + return ctypes.POINTER(cls).from_address(id(iter) + offset) + + +def _get_user_data_as_pyobject(iter): + citer = _CTreeIter.from_iter(iter) + return ctypes.cast(citer.contents.user_data, ctypes.py_object).value + + +def handle_exception(default_return): + """Returns a function which can act as a decorator for wrapping exceptions and + returning "default_return" upon an exception being thrown. + + This is used to wrap Gtk.TreeModel "do_" method implementations so we can return + a proper value from the override upon an exception occurring with client code + implemented by the "on_" methods. + """ + def decorator(func): + def wrapped_func(*args, **kargs): + try: + return func(*args, **kargs) + except BaseException: + # Use excepthook directly to avoid any printing to the screen + # if someone installed an except hook. + sys.excepthook(*sys.exc_info()) + return default_return + return wrapped_func + return decorator + + +class GenericTreeModel(GObject.GObject, Gtk.TreeModel): + + """A base implementation of a Gtk.TreeModel for python. + + The GenericTreeModel eases implementing the Gtk.TreeModel interface in Python. + The class can be subclassed to provide a TreeModel implementation which works + directly with Python objects instead of iterators. + + All of the on_* methods should be overridden by subclasses to provide the + underlying implementation a way to access custom model data. For the purposes of + this API, all custom model data supplied or handed back through the overridable + API will use the argument names: node, parent, and child in regards to user data + python objects. + + The create_tree_iter, set_user_data, invalidate_iters, iter_is_valid methods are + available to help manage Gtk.TreeIter objects and their Python object references. + + GenericTreeModel manages a pool of user data nodes that have been used with iters. + This pool stores a references to user data nodes as a dictionary value with the + key being the integer id of the data. This id is what the Gtk.TreeIter objects + use to reference data in the pool. + References will be removed from the pool when the model is deleted or explicitly + by using the optional "node" argument to the "row_deleted" method when notifying + the model of row deletion. + """ + + leak_references = GObject.Property(default=True, type=bool, + blurb="If True, strong references to user data attached to iters are " + "stored in a dictionary pool (default). Otherwise the user data is " + "stored as a raw pointer to a python object without a reference.") + + # + # Methods + # + def __init__(self): + """Initialize. Make sure to call this from derived classes if overridden.""" + super(GenericTreeModel, self).__init__() + self.stamp = 0 + + #: Dictionary of (id(user_data): user_data), used when leak-refernces=False + self._held_refs = dict() + + # Set initial stamp + self.invalidate_iters() + + def iter_depth_first(self): + """Depth-first iteration of the entire TreeModel yielding the python nodes.""" + stack = collections.deque([None]) + while stack: + it = stack.popleft() + if it is not None: + yield self.get_user_data(it) + children = [self.iter_nth_child(it, i) + for i in range(self.iter_n_children(it))] + stack.extendleft(reversed(children)) + + def invalidate_iter(self, iter): + """Clear user data and its reference from the iter and this model.""" + iter.stamp = 0 + if iter.user_data: + if iter.user_data in self._held_refs: + del self._held_refs[iter.user_data] + iter.user_data = None + + def invalidate_iters(self): + """ + This method invalidates all TreeIter objects associated with this custom tree model + and frees their locally pooled references. + """ + self.stamp = random.randint(-2147483648, 2147483647) + self._held_refs.clear() + + def iter_is_valid(self, iter): + """ + :Returns: + True if the gtk.TreeIter specified by iter is valid for the custom tree model. + """ + return iter.stamp == self.stamp + + def get_user_data(self, iter): + """Get the user_data associated with the given TreeIter. + + GenericTreeModel stores arbitrary Python objects mapped to instances of Gtk.TreeIter. + This method allows to retrieve the Python object held by the given iterator. + """ + if self.leak_references: + return self._held_refs[iter.user_data] + else: + return _get_user_data_as_pyobject(iter) + + def set_user_data(self, iter, user_data): + """Applies user_data and stamp to the given iter. + + If the models "leak_references" property is set, a reference to the + user_data is stored with the model to ensure we don't run into bad + memory problems with the TreeIter. + """ + iter.user_data = id(user_data) + + if user_data is None: + self.invalidate_iter(iter) + else: + iter.stamp = self.stamp + if self.leak_references: + self._held_refs[iter.user_data] = user_data + + def create_tree_iter(self, user_data): + """Create a Gtk.TreeIter instance with the given user_data specific for this model. + + Use this method to create Gtk.TreeIter instance instead of directly calling + Gtk.Treeiter(), this will ensure proper reference managment of wrapped used_data. + """ + iter = Gtk.TreeIter() + self.set_user_data(iter, user_data) + return iter + + def _create_tree_iter(self, data): + """Internal creation of a (bool, TreeIter) pair for returning directly + back to the view interfacing with this model.""" + if data is None: + return (False, None) + else: + it = self.create_tree_iter(data) + return (True, it) + + def row_deleted(self, path, node=None): + """Notify the model a row has been deleted. + + Use the node parameter to ensure the user_data reference associated + with the path is properly freed by this model. + + :Parameters: + path : Gtk.TreePath + Path to the row that has been deleted. + node : object + Python object used as the node returned from "on_get_iter". This is + optional but ensures the model will not leak references to this object. + """ + super(GenericTreeModel, self).row_deleted(path) + node_id = id(node) + if node_id in self._held_refs: + del self._held_refs[node_id] + + # + # GtkTreeModel Interface Implementation + # + @handle_exception(0) + def do_get_flags(self): + """Internal method.""" + return self.on_get_flags() + + @handle_exception(0) + def do_get_n_columns(self): + """Internal method.""" + return self.on_get_n_columns() + + @handle_exception(GObject.TYPE_INVALID) + def do_get_column_type(self, index): + """Internal method.""" + return self.on_get_column_type(index) + + @handle_exception((False, None)) + def do_get_iter(self, path): + """Internal method.""" + return self._create_tree_iter(self.on_get_iter(path)) + + @handle_exception(False) + def do_iter_next(self, iter): + """Internal method.""" + if iter is None: + next_data = self.on_iter_next(None) + else: + next_data = self.on_iter_next(self.get_user_data(iter)) + + self.set_user_data(iter, next_data) + return next_data is not None + + @handle_exception(None) + def do_get_path(self, iter): + """Internal method.""" + path = self.on_get_path(self.get_user_data(iter)) + if path is None: + return None + else: + return Gtk.TreePath(path) + + @handle_exception(None) + def do_get_value(self, iter, column): + """Internal method.""" + return self.on_get_value(self.get_user_data(iter), column) + + @handle_exception((False, None)) + def do_iter_children(self, parent): + """Internal method.""" + data = self.get_user_data(parent) if parent else None + return self._create_tree_iter(self.on_iter_children(data)) + + @handle_exception(False) + def do_iter_has_child(self, parent): + """Internal method.""" + return self.on_iter_has_child(self.get_user_data(parent)) + + @handle_exception(0) + def do_iter_n_children(self, iter): + """Internal method.""" + if iter is None: + return self.on_iter_n_children(None) + return self.on_iter_n_children(self.get_user_data(iter)) + + @handle_exception((False, None)) + def do_iter_nth_child(self, parent, n): + """Internal method.""" + if parent is None: + data = self.on_iter_nth_child(None, n) + else: + data = self.on_iter_nth_child(self.get_user_data(parent), n) + return self._create_tree_iter(data) + + @handle_exception((False, None)) + def do_iter_parent(self, child): + """Internal method.""" + return self._create_tree_iter(self.on_iter_parent(self.get_user_data(child))) + + @handle_exception(None) + def do_ref_node(self, iter): + self.on_ref_node(self.get_user_data(iter)) + + @handle_exception(None) + def do_unref_node(self, iter): + self.on_unref_node(self.get_user_data(iter)) + + # + # Python Subclass Overridables + # + def on_get_flags(self): + """Overridable. + + :Returns Gtk.TreeModelFlags: + The flags for this model. See: Gtk.TreeModelFlags + """ + raise NotImplementedError + + def on_get_n_columns(self): + """Overridable. + + :Returns: + The number of columns for this model. + """ + raise NotImplementedError + + def on_get_column_type(self, index): + """Overridable. + + :Returns: + The column type for the given index. + """ + raise NotImplementedError + + def on_get_iter(self, path): + """Overridable. + + :Returns: + A python object (node) for the given TreePath. + """ + raise NotImplementedError + + def on_iter_next(self, node): + """Overridable. + + :Parameters: + node : object + Node at current level. + + :Returns: + A python object (node) following the given node at the current level. + """ + raise NotImplementedError + + def on_get_path(self, node): + """Overridable. + + :Returns: + A TreePath for the given node. + """ + raise NotImplementedError + + def on_get_value(self, node, column): + """Overridable. + + :Parameters: + node : object + column : int + Column index to get the value from. + + :Returns: + The value of the column for the given node.""" + raise NotImplementedError + + def on_iter_children(self, parent): + """Overridable. + + :Returns: + The first child of parent or None if parent has no children. + If parent is None, return the first node of the model. + """ + raise NotImplementedError + + def on_iter_has_child(self, node): + """Overridable. + + :Returns: + True if the given node has children. + """ + raise NotImplementedError + + def on_iter_n_children(self, node): + """Overridable. + + :Returns: + The number of children for the given node. If node is None, + return the number of top level nodes. + """ + raise NotImplementedError + + def on_iter_nth_child(self, parent, n): + """Overridable. + + :Parameters: + parent : object + n : int + Index of child within parent. + + :Returns: + The child for the given parent index starting at 0. If parent None, + return the top level node corresponding to "n". + If "n" is larger then available nodes, return None. + """ + raise NotImplementedError + + def on_iter_parent(self, child): + """Overridable. + + :Returns: + The parent node of child or None if child is a top level node.""" + raise NotImplementedError + + def on_ref_node(self, node): + pass + + def on_unref_node(self, node): + pass diff --git a/debug-viewer/GstDebugViewer/Common/utils.py b/debug-viewer/GstDebugViewer/Common/utils.py new file mode 100644 index 0000000000..6ba33393ce --- /dev/null +++ b/debug-viewer/GstDebugViewer/Common/utils.py @@ -0,0 +1,333 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Development Utilities +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, see . + +"""GStreamer Development Utilities Common utils module.""" + +import os +import logging +import subprocess as _subprocess + + +class SingletonMeta (type): + + def __init__(cls, name, bases, dict_): + + from weakref import WeakValueDictionary + + super(SingletonMeta, cls).__init__(name, bases, dict_) + + cls._singleton_instances = WeakValueDictionary() + + def __call__(cls, *a, **kw): + + kw_key = tuple(sorted(kw.items())) + + try: + obj = cls._singleton_instances[a + kw_key] + except KeyError: + obj = super(SingletonMeta, cls).__call__(*a, **kw) + cls._singleton_instances[a + kw_key] = obj + return obj + + +def gettext_cache(): + """Return a callable object that operates like gettext.gettext, but is much + faster when a string is looked up more than once. This is very useful in + loops, where calling gettext.gettext can quickly become a major performance + bottleneck.""" + + from gettext import gettext + + d = {} + + def gettext_cache_access(s): + + if s not in d: + d[s] = gettext(s) + return d[s] + + return gettext_cache_access + + +class ClassProperty (property): + + "Like the property class, but also invokes the getter for class access." + + def __init__(self, fget=None, fset=None, fdel=None, doc=None): + + property.__init__(self, fget, fset, fdel, doc) + + self.__fget = fget + + def __get__(self, obj, obj_class=None): + + ret = property.__get__(self, obj, obj_class) + if ret == self: + return self.__fget(None) + else: + return ret + + +class _XDGClass (object): + + """Partial implementation of the XDG Base Directory specification v0.6. + + http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html""" + + def __init__(self): + + self._add_base_dir("DATA_HOME", "~/.local/share") + self._add_base_dir("CONFIG_HOME", "~/.config") + self._add_base_dir("CACHE_HOME", "~/.cache") + + def _add_base_dir(self, name, default): + + dir = os.environ.get("XDG_%s" % (name,)) + if not dir: + dir = os.path.expanduser(os.path.join(*default.split("/"))) + + setattr(self, name, dir) + + +XDG = _XDGClass() + + +class SaveWriteFile (object): + + def __init__(self, filename, mode="wt"): + + from tempfile import mkstemp + + self.logger = logging.getLogger("tempfile") + + dir = os.path.dirname(filename) + base_name = os.path.basename(filename) + temp_prefix = "%s-tmp" % (base_name,) + + if dir: + # Destination dir differs from current directory, ensure that it + # exists: + try: + os.makedirs(dir) + except OSError: + pass + + self.clean_stale(dir, temp_prefix) + + fd, temp_name = mkstemp(dir=dir, prefix=temp_prefix) + + self.target_name = filename + self.temp_name = temp_name + self.real_file = os.fdopen(fd, mode) + + def __enter__(self): + + return self + + def __exit__(self, *exc_args): + + if exc_args == (None, None, None,): + self.close() + else: + self.discard() + + def __del__(self): + + try: + self.discard() + except AttributeError: + # If __init__ failed, self has no real_file attribute. + pass + + def __close_real(self): + + if self.real_file: + self.real_file.close() + self.real_file = None + + def clean_stale(self, dir, temp_prefix): + + from time import time + from glob import glob + + now = time() + pattern = os.path.join(dir, "%s*" % (temp_prefix,)) + + for temp_filename in glob(pattern): + mtime = os.stat(temp_filename).st_mtime + if now - mtime > 3600: + self.logger.info("deleting stale temporary file %s", + temp_filename) + try: + os.unlink(temp_filename) + except EnvironmentError as exc: + self.logger.warning("deleting stale temporary file " + "failed: %s", exc) + + def tell(self, *a, **kw): + + return self.real_file.tell(*a, **kw) + + def write(self, *a, **kw): + + return self.real_file.write(*a, **kw) + + def close(self): + + self.__close_real() + + if self.temp_name: + try: + os.rename(self.temp_name, self.target_name) + except OSError as exc: + import errno + if exc.errno == errno.EEXIST: + # We are probably on windows. + os.unlink(self.target_name) + os.rename(self.temp_name, self.target_name) + self.temp_name = None + + def discard(self): + + self.__close_real() + + if self.temp_name: + + try: + os.unlink(self.temp_name) + except EnvironmentError as exc: + self.logger.warning("deleting temporary file failed: %s", exc) + self.temp_name = None + + +class TeeWriteFile (object): + + # TODO Py2.5: Add context manager methods. + + def __init__(self, *file_objects): + + self.files = list(file_objects) + + def close(self): + + for file in self.files: + file.close() + + def flush(self): + + for file in self.files: + file.flush() + + def write(self, string): + + for file in self.files: + file.write(string) + + def writelines(self, lines): + + for file in self.files: + file.writelines(lines) + + +class FixedPopen (_subprocess.Popen): + + def __init__(self, args, **kw): + + # Unconditionally specify all descriptors as redirected, to + # work around Python bug #1358527 (which is triggered for + # console-less applications on Windows). + + close = [] + + for name in ("stdin", "stdout", "stderr",): + target = kw.get(name) + if not target: + kw[name] = _subprocess.PIPE + close.append(name) + + _subprocess.Popen.__init__(self, args, **kw) + + for name in close: + fp = getattr(self, name) + fp.close() + setattr(self, name, None) + + +class DevhelpError (EnvironmentError): + + pass + + +class DevhelpUnavailableError (DevhelpError): + + pass + + +class DevhelpClient (object): + + def available(self): + + try: + self.version() + except DevhelpUnavailableError: + return False + else: + return True + + def version(self): + + return self._invoke("--version") + + def search(self, entry): + + self._invoke_no_interact("-s", entry) + + def _check_os_error(self, exc): + + import errno + if exc.errno == errno.ENOENT: + raise DevhelpUnavailableError() + + def _invoke(self, *args): + + from subprocess import PIPE + + try: + proc = FixedPopen(("devhelp",) + args, + stdout=PIPE) + except OSError as exc: + self._check_os_error(exc) + raise + + out, err = proc.communicate() + + if proc.returncode is not None and proc.returncode != 0: + raise DevhelpError("devhelp exited with status %i" + % (proc.returncode,)) + return out + + def _invoke_no_interact(self, *args): + + from subprocess import PIPE + + try: + proc = FixedPopen(("devhelp",) + args) + except OSError as exc: + self._check_os_error(exc) + raise diff --git a/debug-viewer/GstDebugViewer/Data.py b/debug-viewer/GstDebugViewer/Data.py new file mode 100644 index 0000000000..796289737c --- /dev/null +++ b/debug-viewer/GstDebugViewer/Data.py @@ -0,0 +1,482 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Debug Viewer - View and analyze GStreamer debug log files +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer Debug Viewer Data module.""" + +import os +import logging +import re +import sys + +# Nanosecond resolution (like Gst.SECOND) +SECOND = 1000000000 + + +def time_args(ts): + + secs = ts // SECOND + + return "%i:%02i:%02i.%09i" % (secs // 60 ** 2, + secs // 60 % 60, + secs % 60, + ts % SECOND,) + + +def time_diff_args(time_diff): + + if time_diff >= 0: + sign = "+" + else: + sign = "-" + + secs = abs(time_diff) // SECOND + + return "%s%02i:%02i.%09i" % (sign, + secs // 60, + secs % 60, + abs(time_diff) % SECOND,) + + +def time_args_no_hours(ts): + + secs = ts // SECOND + + return "%02i:%02i.%09i" % (secs // 60, + secs % 60, + ts % SECOND,) + + +def parse_time(st): + """Parse time strings that look like "0:00:00.0000000".""" + + h, m, s = st.split(":") + secs, subsecs = s.split(".") + + return int((int(h) * 60 ** 2 + int(m) * 60) * SECOND) + \ + int(secs) * SECOND + int(subsecs) + + +class DebugLevel (int): + + __names = ["NONE", "ERROR", "WARN", "FIXME", + "INFO", "DEBUG", "LOG", "TRACE", "MEMDUMP"] + __instances = {} + + def __new__(cls, level): + + try: + level_int = int(level) + except (ValueError, TypeError,): + try: + level_int = cls.__names.index(level.upper()) + except ValueError: + raise ValueError("no debug level named %r" % (level,)) + if level_int in cls.__instances: + return cls.__instances[level_int] + else: + new_instance = int.__new__(cls, level_int) + new_instance.name = cls.__names[level_int] + cls.__instances[level_int] = new_instance + return new_instance + + def __repr__(self): + + return "<%s %s (%i)>" % (type(self).__name__, self.__names[self], self,) + + def higher_level(self): + + if self == len(self.__names) - 1: + raise ValueError("already the highest debug level") + + return DebugLevel(self + 1) + + def lower_level(self): + + if self == 0: + raise ValueError("already the lowest debug level") + + return DebugLevel(self - 1) + + +debug_level_none = DebugLevel("NONE") +debug_level_error = DebugLevel("ERROR") +debug_level_warning = DebugLevel("WARN") +debug_level_info = DebugLevel("INFO") +debug_level_debug = DebugLevel("DEBUG") +debug_level_log = DebugLevel("LOG") +debug_level_fixme = DebugLevel("FIXME") +debug_level_trace = DebugLevel("TRACE") +debug_level_memdump = DebugLevel("MEMDUMP") +debug_levels = [debug_level_none, + debug_level_trace, + debug_level_fixme, + debug_level_log, + debug_level_debug, + debug_level_info, + debug_level_warning, + debug_level_error, + debug_level_memdump] + +# For stripping color codes: +_escape = re.compile(b"\x1b\\[[0-9;]*m") + + +def strip_escape(s): + + # FIXME: This can be optimized further! + + while b"\x1b" in s: + s = _escape.sub(b"", s) + return s + + +def default_log_line_regex_(): + + # "DEBUG " + LEVEL = "([A-Z]+)\s*" + # "0x8165430 " + THREAD = r"(0x[0-9a-f]+)\s+" # r"\((0x[0-9a-f]+) - " + # "0:00:00.777913000 " + TIME = r"(\d+:\d\d:\d\d\.\d+)\s+" + CATEGORY = "([A-Za-z0-9_-]+)\s+" # "GST_REFCOUNTING ", "flacdec " + # " 3089 " + PID = r"(\d+)\s*" + FILENAME = r"([^:]*):" + LINE = r"(\d+):" + FUNCTION = "(~?[A-Za-z0-9_\s\*,\(\)]*):" + # FIXME: When non-g(st)object stuff is logged with *_OBJECT (like + # buffers!), the address is printed *without* <> brackets! + OBJECT = "(?:<([^>]+)>)?" + MESSAGE = "(.+)" + + ANSI = "(?:\x1b\\[[0-9;]*m\\s*)*\\s*" + + # New log format: + expressions = [TIME, ANSI, PID, ANSI, THREAD, ANSI, LEVEL, ANSI, + CATEGORY, FILENAME, LINE, FUNCTION, ANSI, + OBJECT, ANSI, MESSAGE] + # Old log format: + # expressions = [LEVEL, THREAD, TIME, CATEGORY, PID, FILENAME, LINE, + # FUNCTION, OBJECT, MESSAGE] + + return expressions + + +def default_log_line_regex(): + + return re.compile("".join(default_log_line_regex_())) + + +class Producer (object): + + def __init__(self): + + self.consumers = [] + + def have_load_started(self): + + for consumer in self.consumers: + consumer.handle_load_started() + + def have_load_finished(self): + + for consumer in self.consumers: + consumer.handle_load_finished() + + +class SortHelper (object): + + def __init__(self, fileobj, offsets): + + self._gen = self.__gen(fileobj, offsets) + next(self._gen) + + # Override in the instance, for performance (this gets called in an + # inner loop): + self.find_insert_position = self._gen.send + + @staticmethod + def find_insert_position(insert_time_string): + + # Stub for documentary purposes. + + pass + + @staticmethod + def __gen(fileobj, offsets): + + from math import floor + tell = fileobj.tell + seek = fileobj.seek + read = fileobj.read + time_len = len(time_args(0)) + + # We remember the previous insertion point. This gives a nice speed up + # for larger bubbles which are already sorted. TODO: In practice, log + # lines only get out of order across threads. Need to check if it pays + # to parse the thread here and maintain multiple insertion points for + # heavily interleaved parts of the log. + pos = 0 + pos_time_string = "" + + insert_pos = None + while True: + insert_time_string = (yield insert_pos) + + save_offset = tell() + + if pos_time_string <= insert_time_string: + lo = pos + hi = len(offsets) + else: + lo = 0 + hi = pos + + # This is a bisection search, except we don't cut the range in the + # middle each time, but at the 90th percentile. This is because + # logs are "mostly sorted", so the insertion point is much more + # likely to be at the end anyways: + while lo < hi: + mid = int(floor(lo * 0.1 + hi * 0.9)) + seek(offsets[mid]) + mid_time_string = read(time_len) + if insert_time_string.encode('utf8') < mid_time_string: + hi = mid + else: + lo = mid + 1 + pos = lo + # Caller will replace row at pos with the new one, so this is + # correct: + pos_time_string = insert_time_string + + insert_pos = pos + + seek(save_offset) + + +class LineCache (Producer): + """ + offsets: file position for each line + levels: the debug level for each line + """ + + _lines_per_iteration = 50000 + + def __init__(self, fileobj, dispatcher): + + Producer.__init__(self) + + self.logger = logging.getLogger("linecache") + self.dispatcher = dispatcher + + self.__fileobj = fileobj + self.__fileobj.seek(0, 2) + self.__file_size = self.__fileobj.tell() + self.__fileobj.seek(0) + + self.offsets = [] + self.levels = [] # FIXME + + def start_loading(self): + + self.logger.debug("dispatching load process") + self.have_load_started() + self.dispatcher(self.__process()) + + def get_progress(self): + + return float(self.__fileobj.tell()) / self.__file_size + + def __process(self): + + offsets = self.offsets + levels = self.levels + + dict_levels = {"T": debug_level_trace, "F": debug_level_fixme, + "L": debug_level_log, "D": debug_level_debug, + "I": debug_level_info, "W": debug_level_warning, + "E": debug_level_error, " ": debug_level_none, + "M": debug_level_memdump, } + ANSI = "(?:\x1b\\[[0-9;]*m)?" + ANSI_PATTERN = r"\d:\d\d:\d\d\.\d+ " + ANSI + \ + r" *\d+" + ANSI + \ + r" +0x[0-9a-f]+ +" + ANSI + \ + r"([TFLDIEWM ])" + BARE_PATTERN = ANSI_PATTERN.replace(ANSI, "") + rexp_bare = re.compile(BARE_PATTERN) + rexp_ansi = re.compile(ANSI_PATTERN) + rexp = rexp_bare + + # Moving attribute lookups out of the loop: + readline = self.__fileobj.readline + tell = self.__fileobj.tell + rexp_match = rexp.match + levels_append = levels.append + offsets_append = offsets.append + dict_levels_get = dict_levels.get + + self.__fileobj.seek(0) + limit = self._lines_per_iteration + last_line = "" + i = 0 + sort_helper = SortHelper(self.__fileobj, offsets) + find_insert_position = sort_helper.find_insert_position + while True: + i += 1 + if i >= limit: + i = 0 + yield True + + offset = tell() + line = readline().decode('utf-8', errors='replace') + if not line: + break + match = rexp_match(line) + if match is None: + if rexp is rexp_ansi or "\x1b" not in line: + continue + + match = rexp_ansi.match(line) + if match is None: + continue + # Switch to slower ANSI parsing: + rexp = rexp_ansi + rexp_match = rexp.match + + # Timestamp is in the very beginning of the row, and can be sorted + # by lexical comparison. That's why we don't bother parsing the + # time to integer. We also don't have to take a substring here, + # which would be a useless memcpy. + if line >= last_line: + levels_append( + dict_levels_get(match.group(1), debug_level_none)) + offsets_append(offset) + last_line = line + else: + pos = find_insert_position(line) + levels.insert( + pos, dict_levels_get(match.group(1), debug_level_none)) + offsets.insert(pos, offset) + + self.have_load_finished() + yield False + + +class LogLine (list): + + _line_regex = default_log_line_regex() + + @classmethod + def parse_full(cls, line_string): + match = cls._line_regex.match(line_string.decode('utf8', errors='replace')) + if match is None: + # raise ValueError ("not a valid log line (%r)" % (line_string,)) + groups = [0, 0, 0, 0, "", "", 0, "", "", 0] + return cls(groups) + + line = cls(match.groups()) + # Timestamp. + line[0] = parse_time(line[0]) + # PID. + line[1] = int(line[1]) + # Thread. + line[2] = int(line[2], 16) + # Level (this is handled in LineCache). + line[3] = 0 + # Line. + line[6] = int(line[6]) + # Message start offset. + line[9] = match.start(9 + 1) + + for col_id in (4, # COL_CATEGORY + 5, # COL_FILENAME + 7, # COL_FUNCTION, + 8,): # COL_OBJECT + line[col_id] = sys.intern(line[col_id] or "") + + return line + + +class LogLines (object): + + def __init__(self, fileobj, line_cache): + + self.__fileobj = fileobj + self.__line_cache = line_cache + + def __len__(self): + + return len(self.__line_cache.offsets) + + def __getitem__(self, line_index): + + offset = self.__line_cache.offsets[line_index] + self.__fileobj.seek(offset) + line_string = self.__fileobj.readline() + line = LogLine.parse_full(line_string) + msg = line_string[line[-1]:] + line[-1] = msg + return line + + def __iter__(self): + + size = len(self) + i = 0 + while i < size: + yield self[i] + i += 1 + + +class LogFile (Producer): + + def __init__(self, filename, dispatcher): + + import mmap + + Producer.__init__(self) + + self.logger = logging.getLogger("logfile") + + self.path = os.path.normpath(os.path.abspath(filename)) + self.__real_fileobj = open(filename, "rb") + self.fileobj = mmap.mmap( + self.__real_fileobj.fileno(), 0, access=mmap.ACCESS_READ) + self.line_cache = LineCache(self.fileobj, dispatcher) + self.line_cache.consumers.append(self) + + def start_loading(self): + + self.logger.debug("starting load") + self.line_cache.start_loading() + + def get_load_progress(self): + + return self.line_cache.get_progress() + + def handle_load_started(self): + + # Chain up to our consumers: + self.have_load_started() + + def handle_load_finished(self): + self.logger.debug("finish loading") + self.lines = LogLines(self.fileobj, self.line_cache) + + # Chain up to our consumers: + self.have_load_finished() diff --git a/debug-viewer/GstDebugViewer/GUI/__init__.py b/debug-viewer/GstDebugViewer/GUI/__init__.py new file mode 100644 index 0000000000..36b0a34a0d --- /dev/null +++ b/debug-viewer/GstDebugViewer/GUI/__init__.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Debug Viewer - View and analyze GStreamer debug log files +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer Debug Viewer GUI module.""" + +__author__ = u"René Stadler " +__version__ = "0.1" + +import gi + +from GstDebugViewer.GUI.app import App + + +def main(args): + + app = App() + + # TODO: Once we support more than one window, open one window for each + # supplied filename. + window = app.windows[0] + if len(args) > 0: + window.set_log_file(args[0]) + + app.run() + + +if __name__ == "__main__": + main() diff --git a/debug-viewer/GstDebugViewer/GUI/app.py b/debug-viewer/GstDebugViewer/GUI/app.py new file mode 100644 index 0000000000..c6ec2cf02b --- /dev/null +++ b/debug-viewer/GstDebugViewer/GUI/app.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Debug Viewer - View and analyze GStreamer debug log files +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer Debug Viewer GUI module.""" + +import os.path + +import gi +gi.require_version('Gdk', '3.0') +gi.require_version('Gtk', '3.0') + +from gi.repository import GObject +from gi.repository import Gdk +from gi.repository import Gtk + +from GstDebugViewer import Common +from GstDebugViewer.GUI.columns import ViewColumnManager +from GstDebugViewer.GUI.window import Window + + +class AppStateSection (Common.GUI.StateSection): + + _name = "state" + + geometry = Common.GUI.StateInt4("window-geometry") + maximized = Common.GUI.StateBool("window-maximized") + + column_order = Common.GUI.StateItemList("column-order", ViewColumnManager) + columns_visible = Common.GUI.StateItemList( + "columns-visible", ViewColumnManager) + + zoom_level = Common.GUI.StateInt("zoom-level") + + +class AppState (Common.GUI.State): + + def __init__(self, *a, **kw): + + Common.GUI.State.__init__(self, *a, **kw) + + self.add_section_class(AppStateSection) + + +class App (object): + + def __init__(self): + + self.attach() + + def load_plugins(self): + + from GstDebugViewer import Plugins + + plugin_classes = list( + Plugins.load([os.path.dirname(Plugins.__file__)])) + self.plugins = [] + for plugin_class in plugin_classes: + plugin = plugin_class(self) + self.plugins.append(plugin) + + def iter_plugin_features(self): + + for plugin in self.plugins: + for feature in plugin.features: + yield feature + + def attach(self): + + config_home = Common.utils.XDG.CONFIG_HOME + + state_filename = os.path.join( + config_home, "gst-debug-viewer", "state") + + self.state = AppState(state_filename) + self.state_section = self.state.sections["state"] + + self.load_plugins() + + self.windows = [] + + # Apply custom widget stying + # TODO: check for dark theme + css = b""" + @define-color normal_bg_color #FFFFFF; + @define-color shade_bg_color shade(@normal_bg_color, 0.95); + #log_view row:nth-child(even) { + background-color: @normal_bg_color; + } + #log_view row:nth-child(odd) { + background-color: @shade_bg_color; + } + #log_view row:selected { + background-color: #4488FF; + } + #log_view { + -GtkTreeView-horizontal-separator: 0; + -GtkTreeView-vertical-separator: 1; + outline-width: 0; + outline-offset: 0; + } + """ + + style_provider = Gtk.CssProvider() + style_provider.load_from_data(css) + + Gtk.StyleContext.add_provider_for_screen( + Gdk.Screen.get_default(), + style_provider, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + ) + + self.open_window() + + def detach(self): + + # TODO: If we take over deferred saving from the inspector, specify now + # = True here! + self.state.save() + + def run(self): + + try: + Common.Main.MainLoopWrapper(Gtk.main, Gtk.main_quit).run() + except BaseException: + raise + else: + self.detach() + + def open_window(self): + + self.windows.append(Window(self)) + + def close_window(self, window): + + self.windows.remove(window) + if not self.windows: + # GtkTreeView takes some time to go down for large files. Let's block + # until the window is hidden: + GObject.idle_add(Gtk.main_quit) + Gtk.main() + + Gtk.main_quit() diff --git a/debug-viewer/GstDebugViewer/GUI/colors.py b/debug-viewer/GstDebugViewer/GUI/colors.py new file mode 100644 index 0000000000..4d91293dd6 --- /dev/null +++ b/debug-viewer/GstDebugViewer/GUI/colors.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Debug Viewer - View and analyze GStreamer debug log files +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer Debug Viewer GUI module.""" + +from gi.repository import Gtk +from gi.repository import Gdk + +from GstDebugViewer import Data + + +class Color (object): + + def __init__(self, hex_24): + + if hex_24.startswith("#"): + s = hex_24[1:] + else: + s = hex_24 + + self._fields = tuple((int(hs, 16) for hs in (s[:2], s[2:4], s[4:],))) + + def gdk_color(self): + + return Gdk.color_parse(self.hex_string()) + + def hex_string(self): + + return "#%02x%02x%02x" % self._fields + + def float_tuple(self): + + return tuple((float(x) / 255 for x in self._fields)) + + def byte_tuple(self): + + return self._fields + + def short_tuple(self): + + return tuple((x << 8 for x in self._fields)) + + +class ColorPalette (object): + + @classmethod + def get(cls): + + try: + return cls._instance + except AttributeError: + cls._instance = cls() + return cls._instance + + +class TangoPalette (ColorPalette): + + def __init__(self): + + for name, r, g, b in [("black", 0, 0, 0,), + ("white", 255, 255, 255,), + ("butter1", 252, 233, 79), + ("butter2", 237, 212, 0), + ("butter3", 196, 160, 0), + ("chameleon1", 138, 226, 52), + ("chameleon2", 115, 210, 22), + ("chameleon3", 78, 154, 6), + ("orange1", 252, 175, 62), + ("orange2", 245, 121, 0), + ("orange3", 206, 92, 0), + ("skyblue1", 114, 159, 207), + ("skyblue2", 52, 101, 164), + ("skyblue3", 32, 74, 135), + ("plum1", 173, 127, 168), + ("plum2", 117, 80, 123), + ("plum3", 92, 53, 102), + ("chocolate1", 233, 185, 110), + ("chocolate2", 193, 125, 17), + ("chocolate3", 143, 89, 2), + ("scarletred1", 239, 41, 41), + ("scarletred2", 204, 0, 0), + ("scarletred3", 164, 0, 0), + ("aluminium1", 238, 238, 236), + ("aluminium2", 211, 215, 207), + ("aluminium3", 186, 189, 182), + ("aluminium4", 136, 138, 133), + ("aluminium5", 85, 87, 83), + ("aluminium6", 46, 52, 54)]: + setattr(self, name, Color("%02x%02x%02x" % (r, g, b,))) + + +class ColorTheme (object): + + def __init__(self): + + self.colors = {} + + def add_color(self, key, *colors): + + self.colors[key] = colors + + +class LevelColorTheme (ColorTheme): + + pass + + +class LevelColorThemeTango (LevelColorTheme): + + def __init__(self): + + LevelColorTheme.__init__(self) + + p = TangoPalette.get() + self.add_color(Data.debug_level_none, None, None, None) + self.add_color(Data.debug_level_trace, p.black, p.aluminium2) + self.add_color(Data.debug_level_fixme, p.black, p.butter3) + self.add_color(Data.debug_level_log, p.black, p.plum1) + self.add_color(Data.debug_level_debug, p.black, p.skyblue1) + self.add_color(Data.debug_level_info, p.black, p.chameleon1) + self.add_color(Data.debug_level_warning, p.black, p.orange1) + self.add_color(Data.debug_level_error, p.white, p.scarletred1) + self.add_color(Data.debug_level_memdump, p.white, p.aluminium3) + + +class ThreadColorTheme (ColorTheme): + + pass + + +class ThreadColorThemeTango (ThreadColorTheme): + + def __init__(self): + + ThreadColorTheme.__init__(self) + + t = TangoPalette.get() + for i, color in enumerate([t.butter2, + t.orange2, + t.chocolate3, + t.chameleon2, + t.skyblue1, + t.plum1, + t.scarletred1, + t.aluminium6]): + self.add_color(i, color) diff --git a/debug-viewer/GstDebugViewer/GUI/columns.py b/debug-viewer/GstDebugViewer/GUI/columns.py new file mode 100644 index 0000000000..e0867df9c9 --- /dev/null +++ b/debug-viewer/GstDebugViewer/GUI/columns.py @@ -0,0 +1,741 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Debug Viewer - View and analyze GStreamer debug log files +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer Debug Viewer GUI module.""" + +import logging + +from gi.repository import Gtk, GLib + +from GstDebugViewer import Common, Data +from GstDebugViewer.GUI.colors import LevelColorThemeTango +from GstDebugViewer.GUI.models import LazyLogModel, LogModelBase + + +def _(s): + return s + +# Sync with gst-inspector! + + +class Column (object): + + """A single list view column, managed by a ColumnManager instance.""" + + name = None + id = None + label_header = None + get_modify_func = None + get_data_func = None + get_sort_func = None + + def __init__(self): + + view_column = Gtk.TreeViewColumn(self.label_header) + view_column.props.reorderable = True + + self.view_column = view_column + + +class SizedColumn (Column): + + default_size = None + + def compute_default_size(self): + + return None + +# Sync with gst-inspector? + + +class TextColumn (SizedColumn): + + font_family = None + + def __init__(self): + + Column.__init__(self) + + column = self.view_column + cell = Gtk.CellRendererText() + column.pack_start(cell, True) + + cell.props.yalign = 0. + cell.props.ypad = 0 + + if self.font_family: + cell.props.family = self.font_family + cell.props.family_set = True + + if self.get_data_func: + data_func = self.get_data_func() + assert data_func + id_ = self.id + if id_ is not None: + def cell_data_func(column, cell, model, tree_iter, user_data): + data_func(cell.props, model.get_value(tree_iter, id_)) + else: + cell_data_func = data_func + column.set_cell_data_func(cell, cell_data_func) + elif not self.get_modify_func: + column.add_attribute(cell, "text", self.id) + else: + self.update_modify_func(column, cell) + + column.props.resizable = True + + def update_modify_func(self, column, cell): + + modify_func = self.get_modify_func() + id_ = self.id + + def cell_data_func(column, cell, model, tree_iter, user_data): + cell.props.text = modify_func(model.get_value(tree_iter, id_)) + column.set_cell_data_func(cell, cell_data_func) + + def compute_default_size(self): + + values = self.get_values_for_size() + if not values: + return SizedColumn.compute_default_size(self) + + cell = self.view_column.get_cells()[0] + + if self.get_modify_func is not None: + format = self.get_modify_func() + else: + def identity(x): + return x + format = identity + max_width = 0 + for value in values: + cell.props.text = format(value) + x, y, w, h = self.view_column.cell_get_size() + max_width = max(max_width, w) + + return max_width + + def get_values_for_size(self): + + return () + + +class TimeColumn (TextColumn): + + name = "time" + label_header = _("Time") + id = LazyLogModel.COL_TIME + font_family = "monospace" + + def __init__(self, *a, **kw): + + self.base_time = 0 + + TextColumn.__init__(self, *a, **kw) + + def get_modify_func(self): + + if self.base_time: + time_diff_args = Data.time_diff_args + base_time = self.base_time + + def format_time(value): + return time_diff_args(value - base_time) + else: + time_args = Data.time_args + + def format_time(value): + # TODO: This is hard coded to omit hours. + return time_args(value)[2:] + + return format_time + + def get_values_for_size(self): + + values = [0] + + return values + + def set_base_time(self, base_time): + + self.base_time = base_time + + column = self.view_column + cell = column.get_cells()[0] + self.update_modify_func(column, cell) + + +class LevelColumn (TextColumn): + + name = "level" + label_header = _("L") + id = LazyLogModel.COL_LEVEL + + def __init__(self): + + TextColumn.__init__(self) + + cell = self.view_column.get_cells()[0] + cell.props.xalign = .5 + + @staticmethod + def get_modify_func(): + + def format_level(value): + return value.name[0] + + return format_level + + @staticmethod + def get_data_func(): + + theme = LevelColorThemeTango() + colors = dict((level, tuple((c.gdk_color() + for c in theme.colors[level])),) + for level in Data.debug_levels + if level != Data.debug_level_none) + + def level_data_func(cell_props, level): + cell_props.text = level.name[0] + if level in colors: + cell_colors = colors[level] + else: + cell_colors = (None, None, None,) + cell_props.foreground_gdk = cell_colors[0] + cell_props.background_gdk = cell_colors[1] + + return level_data_func + + def get_values_for_size(self): + + values = [Data.debug_level_log, Data.debug_level_debug, + Data.debug_level_info, Data.debug_level_warning, + Data.debug_level_error, Data.debug_level_memdump] + + return values + + +class PidColumn (TextColumn): + + name = "pid" + label_header = _("PID") + id = LazyLogModel.COL_PID + font_family = "monospace" + + @staticmethod + def get_modify_func(): + + return str + + def get_values_for_size(self): + + return ["999999"] + + +class ThreadColumn (TextColumn): + + name = "thread" + label_header = _("Thread") + id = LazyLogModel.COL_THREAD + font_family = "monospace" + + @staticmethod + def get_modify_func(): + + def format_thread(value): + return "0x%07x" % (value,) + + return format_thread + + def get_values_for_size(self): + + return [int("ffffff", 16)] + + +class CategoryColumn (TextColumn): + + name = "category" + label_header = _("Category") + id = LazyLogModel.COL_CATEGORY + + def get_values_for_size(self): + + return ["GST_LONG_CATEGORY", "somelongelement"] + + +class CodeColumn (TextColumn): + + name = "code" + label_header = _("Code") + id = None + + @staticmethod + def get_data_func(): + + filename_id = LogModelBase.COL_FILENAME + line_number_id = LogModelBase.COL_LINE_NUMBER + + def filename_data_func(column, cell, model, tree_iter, user_data): + args = model.get(tree_iter, filename_id, line_number_id) + cell.props.text = "%s:%i" % args + + return filename_data_func + + def get_values_for_size(self): + + return ["gstsomefilename.c:1234"] + + +class FunctionColumn (TextColumn): + + name = "function" + label_header = _("Function") + id = LazyLogModel.COL_FUNCTION + + def get_values_for_size(self): + + return ["gst_this_should_be_enough"] + + +class ObjectColumn (TextColumn): + + name = "object" + label_header = _("Object") + id = LazyLogModel.COL_OBJECT + + def get_values_for_size(self): + + return ["longobjectname00"] + + +class MessageColumn (TextColumn): + + name = "message" + label_header = _("Message") + id = None + + def __init__(self, *a, **kw): + + self.highlighters = {} + + TextColumn.__init__(self, *a, **kw) + + def get_data_func(self): + + highlighters = self.highlighters + id_ = LazyLogModel.COL_MESSAGE + + def message_data_func(column, cell, model, tree_iter, user_data): + + msg = model.get_value(tree_iter, id_).decode("utf8", errors="replace") + + if not highlighters: + cell.props.text = msg + return + + if len(highlighters) > 1: + raise NotImplementedError("FIXME: Support more than one...") + + highlighter = list(highlighters.values())[0] + row = model[tree_iter] + ranges = highlighter(row) + if not ranges: + cell.props.text = msg + else: + tags = [] + prev_end = 0 + end = None + for start, end in ranges: + if prev_end < start: + tags.append( + GLib.markup_escape_text(msg[prev_end:start])) + msg_escape = GLib.markup_escape_text(msg[start:end]) + tags.append("%s" % (msg_escape,)) + prev_end = end + if end is not None: + tags.append(GLib.markup_escape_text(msg[end:])) + cell.props.markup = "".join(tags) + + return message_data_func + + def get_values_for_size(self): + + values = ["Just some good minimum size"] + + return values + + +class ColumnManager (Common.GUI.Manager): + + column_classes = () + + @classmethod + def iter_item_classes(cls): + + return iter(cls.column_classes) + + def __init__(self): + + self.view = None + self.actions = None + self.zoom = 1.0 + self.__columns_changed_id = None + self.columns = [] + self.column_order = list(self.column_classes) + + self.action_group = Gtk.ActionGroup("ColumnActions") + + def make_entry(col_class): + return ("show-%s-column" % (col_class.name,), + None, + col_class.label_header, + None, + None, + None, + True,) + + entries = [make_entry(cls) for cls in self.column_classes] + self.action_group.add_toggle_actions(entries) + + def iter_items(self): + + return iter(self.columns) + + def attach(self): + + for col_class in self.column_classes: + action = self.get_toggle_action(col_class) + if action.props.active: + self._add_column(col_class()) + action.connect("toggled", + self.__handle_show_column_action_toggled, + col_class.name) + + self.__columns_changed_id = self.view.connect("columns-changed", + self.__handle_view_columns_changed) + + def detach(self): + + if self.__columns_changed_id is not None: + self.view.disconnect(self.__columns_changed_id) + self.__columns_changed_id = None + + def attach_sort(self): + + sort_model = self.view.get_model() + + # Inform the sorted tree model of any custom sorting functions. + for col_class in self.column_classes: + if col_class.get_sort_func: + sort_func = col_class.get_sort_func() + sort_model.set_sort_func(col_class.id, sort_func) + + def enable_sort(self): + + sort_model = self.view.get_model() + + if sort_model: + self.logger.debug("activating sort") + sort_model.set_sort_column_id(*self.default_sort) + self.default_sort = None + else: + self.logger.debug("not activating sort (no model set)") + + def disable_sort(self): + + self.logger.debug("deactivating sort") + + sort_model = self.view.get_model() + + self.default_sort = tree_sortable_get_sort_column_id(sort_model) + + sort_model.set_sort_column_id(TREE_SORTABLE_UNSORTED_COLUMN_ID, + Gtk.SortType.ASCENDING) + + def set_zoom(self, scale): + + for column in self.columns: + cell = column.view_column.get_cells()[0] + cell.props.scale = scale + column.view_column.queue_resize() + + self.zoom = scale + + def set_base_time(self, base_time): + + try: + time_column = self.find_item(name=TimeColumn.name) + except KeyError: + return + + time_column.set_base_time(base_time) + self.size_column(time_column) + + def get_toggle_action(self, column_class): + + action_name = "show-%s-column" % (column_class.name,) + return self.action_group.get_action(action_name) + + def get_initial_column_order(self): + + return tuple(self.column_classes) + + def _add_column(self, column): + + name = column.name + pos = self.__get_column_insert_position(column) + + if self.view.props.fixed_height_mode: + column.view_column.props.sizing = Gtk.TreeViewColumnSizing.FIXED + + cell = column.view_column.get_cells()[0] + cell.props.scale = self.zoom + + self.columns.insert(pos, column) + self.view.insert_column(column.view_column, pos) + + def _remove_column(self, column): + + self.columns.remove(column) + self.view.remove_column(column.view_column) + + def __get_column_insert_position(self, column): + + col_class = self.find_item_class(name=column.name) + pos = self.column_order.index(col_class) + before = self.column_order[:pos] + shown_names = [col.name for col in self.columns] + for col_class in before: + if col_class.name not in shown_names: + pos -= 1 + return pos + + def __iter_next_hidden(self, column_class): + + pos = self.column_order.index(column_class) + rest = self.column_order[pos + 1:] + for next_class in rest: + try: + self.find_item(name=next_class.name) + except KeyError: + # No instance -- the column is hidden. + yield next_class + else: + break + + def __handle_show_column_action_toggled(self, toggle_action, name): + + if toggle_action.props.active: + try: + # This should fail. + column = self.find_item(name=name) + except KeyError: + col_class = self.find_item_class(name=name) + self._add_column(col_class()) + else: + # Out of sync for some reason. + return + else: + try: + column = self.find_item(name=name) + except KeyError: + # Out of sync for some reason. + return + else: + self._remove_column(column) + + def __handle_view_columns_changed(self, element_view): + + view_columns = element_view.get_columns() + new_visible = [self.find_item(view_column=column) + for column in view_columns] + + # We only care about reordering here. + if len(new_visible) != len(self.columns): + return + + if new_visible != self.columns: + + new_order = [] + for column in new_visible: + col_class = self.find_item_class(name=column.name) + new_order.append(col_class) + new_order.extend(self.__iter_next_hidden(col_class)) + + names = (column.name for column in new_visible) + self.logger.debug("visible columns reordered: %s", + ", ".join(names)) + + self.columns[:] = new_visible + self.column_order[:] = new_order + + +class ViewColumnManager (ColumnManager): + + column_classes = ( + TimeColumn, LevelColumn, PidColumn, ThreadColumn, CategoryColumn, + CodeColumn, FunctionColumn, ObjectColumn, MessageColumn,) + + default_column_classes = ( + TimeColumn, LevelColumn, CategoryColumn, CodeColumn, + FunctionColumn, ObjectColumn, MessageColumn,) + + def __init__(self, state): + + ColumnManager.__init__(self) + + self.logger = logging.getLogger("ui.columns") + + self.state = state + + def attach(self, view): + + self.view = view + view.connect("notify::model", self.__handle_notify_model) + + order = self.state.column_order + if len(order) == len(self.column_classes): + self.column_order[:] = order + + visible = self.state.columns_visible + if not visible: + visible = self.default_column_classes + for col_class in self.column_classes: + action = self.get_toggle_action(col_class) + action.props.active = (col_class in visible) + + ColumnManager.attach(self) + + self.columns_sized = False + + def detach(self): + + self.state.column_order = self.column_order + self.state.columns_visible = self.columns + + return ColumnManager.detach(self) + + def set_zoom(self, scale): + + ColumnManager.set_zoom(self, scale) + + if self.view is None: + return + + # Timestamp and log level columns are pretty much fixed size, so resize + # them back to default on zoom change: + names = (TimeColumn.name, + LevelColumn.name, + PidColumn.name, + ThreadColumn.name) + for column in self.columns: + if column.name in names: + self.size_column(column) + + def size_column(self, column): + + if column.default_size is None: + default_size = column.compute_default_size() + else: + default_size = column.default_size + # FIXME: Abstract away fixed size setting in Column class! + if default_size is None: + # Dummy fallback: + column.view_column.props.fixed_width = 50 + self.logger.warning( + "%s column does not implement default size", column.name) + else: + column.view_column.props.fixed_width = default_size + + def _add_column(self, column): + + result = ColumnManager._add_column(self, column) + self.size_column(column) + return result + + def _remove_column(self, column): + + column.default_size = column.view_column.props.fixed_width + return ColumnManager._remove_column(self, column) + + def __handle_notify_model(self, view, gparam): + + if self.columns_sized: + # Already sized. + return + model = self.view.get_model() + if model is None: + return + self.logger.debug("model changed, sizing columns") + for column in self.iter_items(): + self.size_column(column) + self.columns_sized = True + + +class WrappingMessageColumn (MessageColumn): + + def wrap_to_width(self, width): + + col = self.view_column + col.props.max_width = width + col.get_cells()[0].props.wrap_width = width + col.queue_resize() + + +class LineViewColumnManager (ColumnManager): + + column_classes = (TimeColumn, WrappingMessageColumn,) + + def __init__(self): + + ColumnManager.__init__(self) + + def attach(self, window): + + self.__size_update = None + + self.view = window.widgets.line_view + self.view.set_size_request(0, 0) + self.view.connect_after("size-allocate", self.__handle_size_allocate) + ColumnManager.attach(self) + + def __update_sizes(self): + + view_width = self.view.get_allocation().width + if view_width == self.__size_update: + # Prevent endless recursion. + return + + self.__size_update = view_width + + col = self.find_item(name="time") + other_width = col.view_column.props.width + + try: + col = self.find_item(name="message") + except KeyError: + return + + width = view_width - other_width + col.wrap_to_width(width) + + def __handle_size_allocate(self, self_, allocation): + + self.__update_sizes() diff --git a/debug-viewer/GstDebugViewer/GUI/filters.py b/debug-viewer/GstDebugViewer/GUI/filters.py new file mode 100644 index 0000000000..0878afa4c7 --- /dev/null +++ b/debug-viewer/GstDebugViewer/GUI/filters.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Debug Viewer - View and analyze GStreamer debug log files +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer Debug Viewer GUI module.""" + +from GstDebugViewer.GUI.models import LogModelBase + + +def get_comparison_function(all_but_this): + + if (all_but_this): + return lambda x, y: x == y + else: + return lambda x, y: x != y + + +class Filter (object): + + pass + + +class DebugLevelFilter (Filter): + + only_this, all_but_this, this_and_above = range(3) + + def __init__(self, debug_level, mode=0): + + col_id = LogModelBase.COL_LEVEL + if mode == self.this_and_above: + def comparison_function(x, y): + return x < y + else: + comparison_function = get_comparison_function( + mode == self.all_but_this) + + def filter_func(row): + return comparison_function(row[col_id], debug_level) + self.filter_func = filter_func + + +class CategoryFilter (Filter): + + def __init__(self, category, all_but_this=False): + + col_id = LogModelBase.COL_CATEGORY + comparison_function = get_comparison_function(all_but_this) + + def category_filter_func(row): + return comparison_function(row[col_id], category) + self.filter_func = category_filter_func + + +class ObjectFilter (Filter): + + def __init__(self, object_, all_but_this=False): + + col_id = LogModelBase.COL_OBJECT + comparison_function = get_comparison_function(all_but_this) + + def object_filter_func(row): + return comparison_function(row[col_id], object_) + self.filter_func = object_filter_func + + +class FunctionFilter (Filter): + + def __init__(self, function_, all_but_this=False): + + col_id = LogModelBase.COL_FUNCTION + comparison_function = get_comparison_function(all_but_this) + + def function_filter_func(row): + return comparison_function(row[col_id], function_) + self.filter_func = function_filter_func + + +class ThreadFilter (Filter): + + def __init__(self, thread_, all_but_this=False): + + col_id = LogModelBase.COL_THREAD + comparison_function = get_comparison_function(all_but_this) + + def thread_filter_func(row): + return comparison_function(row[col_id], thread_) + self.filter_func = thread_filter_func + + +class FilenameFilter (Filter): + + def __init__(self, filename, all_but_this=False): + + col_id = LogModelBase.COL_FILENAME + comparison_function = get_comparison_function(all_but_this) + + def filename_filter_func(row): + return comparison_function(row[col_id], filename) + self.filter_func = filename_filter_func diff --git a/debug-viewer/GstDebugViewer/GUI/models.py b/debug-viewer/GstDebugViewer/GUI/models.py new file mode 100644 index 0000000000..da339162a7 --- /dev/null +++ b/debug-viewer/GstDebugViewer/GUI/models.py @@ -0,0 +1,498 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Debug Viewer - View and analyze GStreamer debug log files +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer Debug Viewer GUI module.""" + +from array import array +from bisect import bisect_left +import logging + +from gi.repository import GObject +from gi.repository import Gtk + +from GstDebugViewer import Common, Data + + +class LogModelBase (Common.GUI.GenericTreeModel, metaclass=Common.GUI.MetaModel): + + columns = ("COL_TIME", GObject.TYPE_UINT64, + "COL_PID", int, + "COL_THREAD", GObject.TYPE_UINT64, + "COL_LEVEL", object, + "COL_CATEGORY", str, + "COL_FILENAME", str, + "COL_LINE_NUMBER", int, + "COL_FUNCTION", str, + "COL_OBJECT", str, + "COL_MESSAGE", str,) + + def __init__(self): + + Common.GUI.GenericTreeModel.__init__(self) + + # self.props.leak_references = False + + self.line_offsets = array("I") + self.line_levels = [] # FIXME: Not so nice! + self.line_cache = {} + + def ensure_cached(self, line_offset): + + raise NotImplementedError("derived classes must override this method") + + def access_offset(self, offset): + + raise NotImplementedError("derived classes must override this method") + + def iter_rows_offset(self): + + ensure_cached = self.ensure_cached + line_cache = self.line_cache + line_levels = self.line_levels + COL_LEVEL = self.COL_LEVEL + COL_MESSAGE = self.COL_MESSAGE + access_offset = self.access_offset + + for i, offset in enumerate(self.line_offsets): + ensure_cached(offset) + row = line_cache[offset] + # adjust special rows + row[COL_LEVEL] = line_levels[i] + msg_offset = row[COL_MESSAGE] + row[COL_MESSAGE] = access_offset(offset + msg_offset) + yield (row, offset,) + row[COL_MESSAGE] = msg_offset + + def on_get_flags(self): + + flags = Gtk.TreeModelFlags.LIST_ONLY | Gtk.TreeModelFlags.ITERS_PERSIST + + return flags + + def on_get_n_columns(self): + + return len(self.column_types) + + def on_get_column_type(self, col_id): + + return self.column_types[col_id] + + def on_get_iter(self, path): + + if not path: + return + + if len(path) > 1: + # Flat model. + return None + + line_index = path[0] + + if line_index > len(self.line_offsets) - 1: + return None + + return line_index + + def on_get_path(self, rowref): + + line_index = rowref + + return (line_index,) + + def on_get_value(self, line_index, col_id): + + last_index = len(self.line_offsets) - 1 + + if line_index > last_index: + return None + + if col_id == self.COL_LEVEL: + return self.line_levels[line_index] + + line_offset = self.line_offsets[line_index] + self.ensure_cached(line_offset) + + value = self.line_cache[line_offset][col_id] + if col_id == self.COL_MESSAGE: + # strip whitespace + newline + value = self.access_offset(line_offset + value).strip() + elif col_id in (self.COL_TIME, self.COL_THREAD): + value = GObject.Value(GObject.TYPE_UINT64, value) + + return value + + def get_value_range(self, col_id, start, stop): + + if col_id != self.COL_LEVEL: + raise NotImplementedError("XXX FIXME") + + return self.line_levels[start:stop] + + def on_iter_next(self, line_index): + + last_index = len(self.line_offsets) - 1 + + if line_index >= last_index: + return None + else: + return line_index + 1 + + def on_iter_children(self, parent): + + return self.on_iter_nth_child(parent, 0) + + def on_iter_has_child(self, rowref): + + return False + + def on_iter_n_children(self, rowref): + + if rowref is not None: + return 0 + + return len(self.line_offsets) + + def on_iter_nth_child(self, parent, n): + + last_index = len(self.line_offsets) - 1 + + if parent or n > last_index: + return None + + return n + + def on_iter_parent(self, child): + + return None + + # def on_ref_node (self, rowref): + + # pass + + # def on_unref_node (self, rowref): + + # pass + + +class LazyLogModel (LogModelBase): + + def __init__(self, log_obj=None): + + LogModelBase.__init__(self) + + self.__log_obj = log_obj + + if log_obj: + self.set_log(log_obj) + + def set_log(self, log_obj): + + self.__fileobj = log_obj.fileobj + + self.line_cache.clear() + self.line_offsets = log_obj.line_cache.offsets + self.line_levels = log_obj.line_cache.levels + + def access_offset(self, offset): + + # TODO: Implement using one slice access instead of seek+readline. + self.__fileobj.seek(offset) + return self.__fileobj.readline() + + def ensure_cached(self, line_offset): + + if line_offset in self.line_cache: + return + + if len(self.line_cache) > 10000: + self.line_cache.clear() + + self.__fileobj.seek(line_offset) + line = self.__fileobj.readline() + + self.line_cache[line_offset] = Data.LogLine.parse_full(line) + + +class FilteredLogModelBase (LogModelBase): + + def __init__(self, super_model): + + LogModelBase.__init__(self) + + self.logger = logging.getLogger("filter-model-base") + + self.super_model = super_model + self.access_offset = super_model.access_offset + self.ensure_cached = super_model.ensure_cached + self.line_cache = super_model.line_cache + + def line_index_to_super(self, line_index): + + raise NotImplementedError("index conversion not supported") + + def line_index_from_super(self, super_line_index): + + raise NotImplementedError("index conversion not supported") + + +class FilteredLogModel (FilteredLogModelBase): + + def __init__(self, super_model): + + FilteredLogModelBase.__init__(self, super_model) + + self.logger = logging.getLogger("filtered-log-model") + + self.filters = [] + self.reset() + self.__active_process = None + self.__filter_progress = 0. + + def reset(self): + + self.logger.debug("reset filter") + + self.line_offsets = self.super_model.line_offsets + self.line_levels = self.super_model.line_levels + self.super_index = range(len(self.line_offsets)) + + del self.filters[:] + + def __filter_process(self, filter): + + YIELD_LIMIT = 10000 + + self.logger.debug("preparing new filter") + new_line_offsets = array("I") + new_line_levels = [] + new_super_index = array("I") + level_id = self.COL_LEVEL + func = filter.filter_func + + def enum(): + i = 0 + for row, offset in self.iter_rows_offset(): + line_index = self.super_index[i] + yield (line_index, row, offset,) + i += 1 + self.logger.debug("running filter") + progress = 0. + progress_full = float(len(self)) + y = YIELD_LIMIT + for i, row, offset in enum(): + if func(row): + new_line_offsets.append(offset) + new_line_levels.append(row[level_id]) + new_super_index.append(i) + y -= 1 + if y == 0: + progress += float(YIELD_LIMIT) + self.__filter_progress = progress / progress_full + y = YIELD_LIMIT + yield True + self.line_offsets = new_line_offsets + self.line_levels = new_line_levels + self.super_index = new_super_index + self.logger.debug("filtering finished") + + self.__filter_progress = 1. + self.__handle_filter_process_finished() + yield False + + def add_filter(self, filter, dispatcher): + + if self.__active_process is not None: + raise ValueError("dispatched a filter process already") + + self.logger.debug("adding filter") + + self.filters.append(filter) + + self.__dispatcher = dispatcher + self.__active_process = self.__filter_process(filter) + dispatcher(self.__active_process) + + def abort_process(self): + + if self.__active_process is None: + raise ValueError("no filter process running") + + self.__dispatcher.cancel() + self.__active_process = None + self.__dispatcher = None + + del self.filters[-1] + + def get_filter_progress(self): + + if self.__active_process is None: + raise ValueError("no filter process running") + + return self.__filter_progress + + def __handle_filter_process_finished(self): + + self.__active_process = None + self.handle_process_finished() + + def handle_process_finished(self): + + pass + + def line_index_from_super(self, super_line_index): + + return bisect_left(self.super_index, super_line_index) + + def line_index_to_super(self, line_index): + + return self.super_index[line_index] + + def set_range(self, super_start, super_stop): + + old_super_start = self.line_index_to_super(0) + old_super_stop = self.line_index_to_super( + len(self.super_index) - 1) + 1 + + self.logger.debug("set range (%i, %i), current (%i, %i)", + super_start, super_stop, old_super_start, old_super_stop) + + if len(self.filters) == 0: + # Identity. + self.super_index = range(super_start, super_stop) + self.line_offsets = SubRange(self.super_model.line_offsets, + super_start, super_stop) + self.line_levels = SubRange(self.super_model.line_levels, + super_start, super_stop) + return + + if super_start < old_super_start: + # TODO: + raise NotImplementedError("Only handling further restriction of the range" + " (start offset = %i)" % (super_start,)) + + if super_stop > old_super_stop: + # TODO: + raise NotImplementedError("Only handling further restriction of the range" + " (end offset = %i)" % (super_stop,)) + + start = self.line_index_from_super(super_start) + stop = self.line_index_from_super(super_stop) + + self.super_index = SubRange(self.super_index, start, stop) + self.line_offsets = SubRange(self.line_offsets, start, stop) + self.line_levels = SubRange(self.line_levels, start, stop) + + +class SubRange (object): + + __slots__ = ("size", "start", "stop",) + + def __init__(self, size, start, stop): + + if start > stop: + raise ValueError( + "need start <= stop (got %r, %r)" % (start, stop,)) + + if isinstance(size, type(self)): + # Another SubRange, don't stack: + start += size.start + stop += size.start + size = size.size + + self.size = size + self.start = start + self.stop = stop + + def __getitem__(self, i): + + if isinstance(i, slice): + stop = i.stop + if stop >= 0: + stop += self.start + else: + stop += self.stop + + return self.size[i.start + self.start:stop] + else: + return self.size[i + self.start] + + def __len__(self): + + return self.stop - self.start + + def __iter__(self): + + size = self.size + for i in range(self.start, self.stop): + yield size[i] + + +class LineViewLogModel (FilteredLogModelBase): + + def __init__(self, super_model): + + FilteredLogModelBase.__init__(self, super_model) + + self.line_offsets = [] + self.line_levels = [] + + self.parent_indices = [] + + def reset(self): + + del self.line_offsets[:] + del self.line_levels[:] + + def line_index_to_super(self, line_index): + + return self.parent_indices[line_index] + + def insert_line(self, position, super_line_index): + + if position == -1: + position = len(self.line_offsets) + li = super_line_index + self.line_offsets.insert(position, self.super_model.line_offsets[li]) + self.line_levels.insert(position, self.super_model.line_levels[li]) + self.parent_indices.insert(position, super_line_index) + + path = (position,) + tree_iter = self.get_iter(path) + self.row_inserted(path, tree_iter) + + def replace_line(self, line_index, super_line_index): + + li = line_index + self.line_offsets[li] = self.super_model.line_offsets[super_line_index] + self.line_levels[li] = self.super_model.line_levels[super_line_index] + self.parent_indices[li] = super_line_index + + path = (line_index,) + tree_iter = self.get_iter(path) + self.row_changed(path, tree_iter) + + def remove_line(self, line_index): + + for l in (self.line_offsets, + self.line_levels, + self.parent_indices,): + del l[line_index] + + path = (line_index,) + self.row_deleted(path) diff --git a/debug-viewer/GstDebugViewer/GUI/window.py b/debug-viewer/GstDebugViewer/GUI/window.py new file mode 100644 index 0000000000..43ed9c0810 --- /dev/null +++ b/debug-viewer/GstDebugViewer/GUI/window.py @@ -0,0 +1,1068 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Debug Viewer - View and analyze GStreamer debug log files +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer Debug Viewer GUI module.""" + +import os.path +import tempfile +import shutil + +from bisect import bisect_right, bisect_left +import logging + +from gi.repository import GObject +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import GLib + +from GstDebugViewer import Common, Data, Main +from GstDebugViewer.GUI.columns import LineViewColumnManager, ViewColumnManager +from GstDebugViewer.GUI.filters import (CategoryFilter, + DebugLevelFilter, + FilenameFilter, + FunctionFilter, + ThreadFilter, + ObjectFilter) +from GstDebugViewer.GUI.models import (FilteredLogModel, + LazyLogModel, + LineViewLogModel, + LogModelBase) + + +ZOOM_FACTOR = 1.15 + + +def _(s): + return s + + +def action(func): + + func.is_action_handler = True + + return func + + +def iter_actions(manager): + + cls = type(manager) + it = cls.__dict__.items() + for name, member in it: + try: + member.is_action_handler + except AttributeError: + continue + + bound_method = getattr(manager, name) + + assert name.startswith("handle_") + assert name.endswith("_action_activate") + action_name = name[len("handle_"):-len("_action_activate")] + action_name = action_name.replace("_", "-") + + yield (action_name, bound_method,) + + +class LineView (object): + + def __init__(self): + + self.column_manager = LineViewColumnManager() + + def attach(self, window): + + for action_name, handler in iter_actions(self): + action = getattr(window.actions, action_name) + action.connect("activate", handler) + + self.clear_action = window.actions.clear_line_view + + self.line_view = window.widgets.line_view + self.line_view.connect( + "row-activated", self.handle_line_view_row_activated) + + ui = window.ui_manager + self.popup = ui.get_widget( + "/ui/context/LineViewContextMenu").get_submenu() + Common.GUI.widget_add_popup_menu(self.line_view, self.popup) + + self.log_view = log_view = window.log_view + log_view.connect("row-activated", self.handle_log_view_row_activated) + sel = log_view.get_selection() + sel.connect("changed", self.handle_log_view_selection_changed) + + self.clear_action.props.sensitive = False + self.column_manager.attach(window) + + def clear(self): + + model = self.line_view.get_model() + + if len(model) == 0: + return + + for i in range(1, len(model)): + model.remove_line(1) + + self.clear_action.props.sensitive = False + + def handle_attach_log_file(self, window): + + self.line_view.set_model(LineViewLogModel(window.log_model)) + + def handle_line_view_row_activated(self, view, path, column): + + line_index = path[0] + line_model = view.get_model() + log_model = self.log_view.get_model() + super_index = line_model.line_index_to_super(line_index) + log_index = log_model.line_index_from_super(super_index) + path = (log_index,) + self.log_view.scroll_to_cell(path, use_align=True, row_align=.5) + sel = self.log_view.get_selection() + sel.select_path(path) + + def handle_log_view_row_activated(self, view, path, column): + + log_model = view.get_model() + line_index = path[0] + + super_index = log_model.line_index_to_super(line_index) + line_model = self.line_view.get_model() + if line_model is None: + return + + if len(line_model): + timestamps = [row[line_model.COL_TIME] for row in line_model] + row = log_model[(line_index,)] + position = bisect_right(timestamps, row[line_model.COL_TIME]) + else: + position = 0 + if len(line_model) > 1: + other_index = line_model.line_index_to_super(position - 1) + else: + other_index = -1 + if other_index == super_index and position != 1: + # Already have the line. + pass + else: + line_model.insert_line(position, super_index) + self.clear_action.props.sensitive = True + + def handle_log_view_selection_changed(self, selection): + + line_model = self.line_view.get_model() + if line_model is None: + return + + model, tree_iter = selection.get_selected() + + if tree_iter is None: + return + + path = model.get_path(tree_iter) + line_index = model.line_index_to_super(path[0]) + + if len(line_model) == 0: + line_model.insert_line(0, line_index) + else: + line_model.replace_line(0, line_index) + + @action + def handle_clear_line_view_action_activate(self, action): + + self.clear() + + +class ProgressDialog (object): + + def __init__(self, window, title=""): + + bar = Gtk.InfoBar() + bar.props.message_type = Gtk.MessageType.INFO + bar.connect("response", self.__handle_info_bar_response) + bar.add_button(Gtk.STOCK_CANCEL, 1) + area_box = bar.get_content_area() + box = Gtk.HBox(spacing=8) + + box.pack_start(Gtk.Label(label=title), False, False, 0) + + progress = Gtk.ProgressBar() + box.pack_start(progress, False, False, 0) + + area_box.pack_start(box, False, False, 0) + + self.widget = bar + self.__progress_bar = progress + + def __handle_info_bar_response(self, info_bar, response): + + self.handle_cancel() + + def handle_cancel(self): + + pass + + def update(self, progress): + + if self.__progress_bar is None: + return + + self.__progress_bar.props.fraction = progress + + +class Window (object): + + def __init__(self, app): + + self.logger = logging.getLogger("ui.window") + self.app = app + + self.tmpfile = None + self.dispatcher = None + self.info_widget = None + self.progress_dialog = None + self.update_progress_id = None + + self.window_state = Common.GUI.WindowState() + self.column_manager = ViewColumnManager(app.state_section) + + self.actions = Common.GUI.Actions() + + group = Gtk.ActionGroup("MenuActions") + group.add_actions([("AppMenuAction", None, _("_Application")), + ("ViewMenuAction", None, _("_View")), + ("ViewColumnsMenuAction", None, _("_Columns")), + ("HelpMenuAction", None, _("_Help")), + ("LineViewContextMenuAction", None, "")]) + self.actions.add_group(group) + + group = Gtk.ActionGroup("WindowActions") + group.add_actions( + [("new-window", Gtk.STOCK_NEW, _("_New Window"), "N"), + ("open-file", Gtk.STOCK_OPEN, _( + "_Open File"), "O"), + ("reload-file", Gtk.STOCK_REFRESH, _( + "_Reload File"), "R"), + ("close-window", Gtk.STOCK_CLOSE, _( + "Close _Window"), "W"), + ("cancel-load", Gtk.STOCK_CANCEL, None,), + ("clear-line-view", Gtk.STOCK_CLEAR, None), + ("show-about", None, _( + "About GStreamer Debug Viewer",)), + ("enlarge-text", Gtk.STOCK_ZOOM_IN, _( + "Enlarge Text"), "plus"), + ("shrink-text", Gtk.STOCK_ZOOM_OUT, _( + "Shrink Text"), "minus"), + ("reset-text", Gtk.STOCK_ZOOM_100, _("Normal Text Size"), "0")]) + self.actions.add_group(group) + self.actions.reload_file.props.sensitive = False + + group = Gtk.ActionGroup("RowActions") + group.add_actions( + [("hide-before-line", None, _("Hide lines before this point")), + ("hide-after-line", None, _( + "Hide lines after this point")), + ("show-hidden-lines", None, _( + "Show hidden lines")), + ("edit-copy-line", Gtk.STOCK_COPY, _( + "Copy line"), "C"), + ("edit-copy-message", Gtk.STOCK_COPY, _( + "Copy message"), ""), + ("set-base-time", None, _("Set base time")), + ("hide-log-level", None, _("Hide log level")), + ("hide-log-level-and-above", None, _( + "Hide this log level and above")), + ("show-only-log-level", None, _( + "Show only log level")), + ("hide-log-category", None, _( + "Hide log category")), + ("show-only-log-category", None, _( + "Show only log category")), + ("hide-thread", None, _( + "Hide thread")), + ("show-only-thread", None, _( + "Show only thread")), + ("hide-object", None, _("Hide object")), + ("show-only-object", None, _( + "Show only object")), + ("hide-function", None, _("Hide function")), + ("show-only-function", None, _( + "Show only function")), + ("hide-filename", None, _("Hide filename")), + ("show-only-filename", None, _("Show only filename"))]) + group.props.sensitive = False + self.actions.add_group(group) + + self.actions.add_group(self.column_manager.action_group) + + self.log_file = None + self.log_model = None + self.log_filter = None + + self.widget_factory = Common.GUI.WidgetFactory(Main.Paths.data_dir) + self.widgets = self.widget_factory.make("main-window.ui", "main_window") + + ui_filename = os.path.join(Main.Paths.data_dir, "menus.ui") + self.ui_factory = Common.GUI.UIFactory(ui_filename, self.actions) + + self.ui_manager = ui = self.ui_factory.make() + menubar = ui.get_widget("/ui/menubar") + self.widgets.vbox_main.pack_start(menubar, False, False, 0) + + self.gtk_window = self.widgets.main_window + self.gtk_window.add_accel_group(ui.get_accel_group()) + self.log_view = self.widgets.log_view + self.log_view.drag_dest_unset() + self.log_view.set_search_column(-1) + sel = self.log_view.get_selection() + sel.connect("changed", self.handle_log_view_selection_changed) + + self.view_popup = ui.get_widget( + "/ui/context/LogViewContextMenu").get_submenu() + Common.GUI.widget_add_popup_menu(self.log_view, self.view_popup) + + # Widgets to set insensitive when the window is considered as + # such. This is done during loading/filtering, where we can't set the + # whole window insensitive because the progress info bar should be + # usable to allow cancellation. + self.main_sensitivity = [menubar] + self.main_sensitivity.extend(self.widgets.vbox_main.get_children()) + + self.line_view = LineView() + + self.attach() + self.column_manager.attach(self.log_view) + + def setup_model(self, model): + + self.log_model = model + self.log_filter = FilteredLogModel(self.log_model) + self.log_filter.handle_process_finished = self.handle_log_filter_process_finished + + def get_top_attach_point(self): + + return self.widgets.vbox_main + + def get_side_attach_point(self): + + return self.widgets.hbox_view + + def attach(self): + + self.zoom_level = 0 + zoom_percent = self.app.state_section.zoom_level + if zoom_percent: + self.restore_zoom(float(zoom_percent) / 100.) + + self.window_state.attach(window=self.gtk_window, + state=self.app.state_section) + + self.clipboard = Gtk.Clipboard.get_for_display( + self.gtk_window.get_display(), + Gdk.SELECTION_CLIPBOARD) + + for action_name, handler in iter_actions(self): + action = getattr(self.actions, action_name) + action.connect("activate", handler) + + self.gtk_window.connect( + "delete-event", self.handle_window_delete_event) + + self.features = [] + + for plugin_feature in self.app.iter_plugin_features(): + feature = plugin_feature(self.app) + self.features.append(feature) + + for feature in self.features: + feature.handle_attach_window(self) + + # FIXME: With multiple selection mode, browsing the list with key + # up/down slows to a crawl! WTF is wrong with this stupid widget??? + sel = self.log_view.get_selection() + sel.set_mode(Gtk.SelectionMode.BROWSE) + + self.line_view.attach(self) + + # Do not translate; fallback application name for e.g. gnome-shell if + # the desktop file is not installed: + self.gtk_window.set_wmclass( + "gst-debug-viewer", "GStreamer Debug Viewer") + + self.gtk_window.show() + + def detach(self): + + self.set_log_file(None) + for feature in self.features: + feature.handle_detach_window(self) + + self.window_state.detach() + self.column_manager.detach() + + def get_active_line_index(self): + + selection = self.log_view.get_selection() + model, tree_iter = selection.get_selected() + if tree_iter is None: + raise ValueError("no line selected") + path = model.get_path(tree_iter) + return path[0] + + def get_active_line(self): + + selection = self.log_view.get_selection() + model, tree_iter = selection.get_selected() + if tree_iter is None: + raise ValueError("no line selected") + model = self.log_view.get_model() + return model.get(tree_iter, *LogModelBase.column_ids) + + def close(self, *a, **kw): + + self.logger.debug("closing window, detaching") + self.detach() + self.gtk_window.hide() + self.logger.debug("requesting close from app") + self.app.close_window(self) + + def push_view_state(self): + + self.default_index = None + self.default_start_index = None + + model = self.log_view.get_model() + if model is None: + return + + try: + line_index = self.get_active_line_index() + except ValueError: + super_index = None + self.logger.debug("no line selected") + else: + super_index = model.line_index_to_super(line_index) + self.logger.debug("pushing selected line %i (abs %i)", + line_index, super_index) + + self.default_index = super_index + + vis_range = self.log_view.get_visible_range() + if vis_range is not None: + start_path, end_path = vis_range + start_index = start_path[0] + self.default_start_index = model.line_index_to_super(start_index) + + def update_model(self, model=None): + + if model is None: + model = self.log_view.get_model() + + previous_model = self.log_view.get_model() + + if previous_model == model: + # Force update. + self.log_view.set_model(None) + self.log_view.set_model(model) + + def pop_view_state(self, scroll_to_selection=False): + + model = self.log_view.get_model() + if model is None: + return + + selected_index = self.default_index + start_index = self.default_start_index + + if selected_index is not None: + + try: + select_index = model.line_index_from_super(selected_index) + except IndexError as exc: + self.logger.debug( + "abs line index %i filtered out, not reselecting", + selected_index) + else: + assert select_index >= 0 + sel = self.log_view.get_selection() + path = (select_index,) + sel.select_path(path) + + if start_index is None or scroll_to_selection: + self.log_view.scroll_to_cell( + path, use_align=True, row_align=.5) + + if start_index is not None and not scroll_to_selection: + + def traverse(): + for i in range(start_index, len(model)): + yield i + for i in range(start_index - 1, 0, -1): + yield i + for current_index in traverse(): + try: + target_index = model.line_index_from_super(current_index) + except IndexError: + continue + else: + path = (target_index,) + self.log_view.scroll_to_cell( + path, use_align=True, row_align=0.) + break + + def update_view(self): + + view = self.log_view + model = view.get_model() + + start_path, end_path = view.get_visible_range() + start_index, end_index = start_path[0], end_path[0] + + for line_index in range(start_index, end_index + 1): + path = (line_index,) + tree_iter = model.get_iter(path) + model.row_changed(path, tree_iter) + + def handle_log_view_selection_changed(self, selection): + + try: + line_index = self.get_active_line_index() + except ValueError: + first_selected = True + last_selected = True + else: + first_selected = (line_index == 0) + last_selected = ( + line_index == len(self.log_view.get_model()) - 1) + + self.actions.hide_before_line.props.sensitive = not first_selected + self.actions.hide_after_line.props.sensitive = not last_selected + + def handle_window_delete_event(self, window, event): + + self.actions.close_window.activate() + + return True + + @action + def handle_new_window_action_activate(self, action): + + self.app.open_window() + + @action + def handle_open_file_action_activate(self, action): + + dialog = Gtk.FileChooserDialog(None, self.gtk_window, + Gtk.FileChooserAction.OPEN, + (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, + Gtk.STOCK_OPEN, Gtk.ResponseType.ACCEPT,)) + response = dialog.run() + dialog.hide() + if response == Gtk.ResponseType.ACCEPT: + self.set_log_file(dialog.get_filename()) + dialog.destroy() + + @action + def handle_reload_file_action_activate(self, action): + + if self.log_file is None: + return + + self.set_log_file(self.log_file.path) + + @action + def handle_cancel_load_action_activate(self, action): + + self.logger.debug("cancelling data load") + + self.set_log_file(None) + + if self.progress_dialog is not None: + self.hide_info() + self.progress_dialog = None + if self.update_progress_id is not None: + GObject.source_remove(self.update_progress_id) + self.update_progress_id = None + + self.set_sensitive(True) + + @action + def handle_close_window_action_activate(self, action): + + self.close() + + @action + def handle_hide_after_line_action_activate(self, action): + + self.hide_range(after=True) + + @action + def handle_hide_before_line_action_activate(self, action): + + self.hide_range(after=False) + + def hide_range(self, after): + + model = self.log_view.get_model() + try: + filtered_line_index = self.get_active_line_index() + except ValueError: + return + + if after: + first_index = model.line_index_to_super(0) + last_index = model.line_index_to_super(filtered_line_index) + + self.logger.info( + "hiding lines after %i (abs %i), first line is abs %i", + filtered_line_index, + last_index, + first_index) + else: + first_index = model.line_index_to_super(filtered_line_index) + last_index = model.line_index_to_super(len(model) - 1) + + self.logger.info( + "hiding lines before %i (abs %i), last line is abs %i", + filtered_line_index, + first_index, + last_index) + + self.push_view_state() + start_index = first_index + stop_index = last_index + 1 + self.log_filter.set_range(start_index, stop_index) + self.update_model() + self.pop_view_state() + self.actions.show_hidden_lines.props.sensitive = True + + def get_range(self): + + view = self.log_view + model = view.get_model() + visible_range = view.get_visible_range() + if visible_range is None: + return None + start_path, end_path = visible_range + if not start_path or not end_path: + return None + ts1 = model.get_value(model.get_iter(start_path), + model.COL_TIME) + ts2 = model.get_value(model.get_iter(end_path), + model.COL_TIME) + return (ts1, ts2) + + @action + def handle_show_hidden_lines_action_activate(self, action): + + self.logger.info("restoring model filter to show all lines") + self.push_view_state() + self.log_view.set_model(None) + self.log_filter.reset() + self.update_model(self.log_filter) + self.pop_view_state(scroll_to_selection=True) + self.actions.show_hidden_lines.props.sensitive = False + + @action + def handle_edit_copy_line_action_activate(self, action): + + line_index = self.get_active_line_index() + model = self.log_view.get_model() + line_offset = model.line_offsets[line_index] + + line_text = model.access_offset(line_offset).strip() + line_text = Data.strip_escape(line_text) + + self.clipboard.set_text(line_text.decode('utf8', errors='replace'), -1) + + @action + def handle_edit_copy_message_action_activate(self, action): + + col_id = LogModelBase.COL_MESSAGE + self.clipboard.set_text(self.get_active_line()[ + col_id].decode('utf8'), -1) + + @action + def handle_enlarge_text_action_activate(self, action): + + self.update_zoom_level(1) + + @action + def handle_shrink_text_action_activate(self, action): + + self.update_zoom_level(-1) + + @action + def handle_reset_text_action_activate(self, action): + + self.update_zoom_level(-self.zoom_level) + + def restore_zoom(self, scale): + + from math import log + + self.zoom_level = int(round(log(scale) / log(ZOOM_FACTOR))) + + self.column_manager.set_zoom(scale) + + def update_zoom_level(self, delta_step): + + if not delta_step: + return + + self.zoom_level += delta_step + scale = ZOOM_FACTOR ** self.zoom_level + + self.column_manager.set_zoom(scale) + + self.app.state_section.zoom_level = int(round(scale * 100.)) + + def set_sensitive(self, sensitive): + + for widget in self.main_sensitivity: + widget.props.sensitive = sensitive + + def show_info(self, widget): + + self.hide_info() + + box = self.widgets.vbox_main + box.pack_start(widget, False, False, 0) + box.reorder_child(widget, 2) + widget.show_all() + self.info_widget = widget + + def hide_info(self): + + if self.info_widget is None: + return + + self.info_widget.destroy() + self.info_widget = None + + def add_model_filter(self, filter): + + self.progress_dialog = ProgressDialog(self, _("Filtering")) + self.show_info(self.progress_dialog.widget) + self.progress_dialog.handle_cancel = self.handle_filter_progress_dialog_cancel + dispatcher = Common.Data.GSourceDispatcher() + + # FIXME: Unsetting the model to keep e.g. the dispatched timeline + # sentinel from collecting data while we filter idly, which slows + # things down for nothing. + self.push_view_state() + self.log_view.set_model(None) + self.log_filter.add_filter(filter, dispatcher=dispatcher) + + GObject.timeout_add(250, self.update_filter_progress) + + self.set_sensitive(False) + + def update_filter_progress(self): + + if self.progress_dialog is None: + return False + + try: + progress = self.log_filter.get_filter_progress() + except ValueError: + self.logger.warning("no filter process running") + return False + + self.progress_dialog.update(progress) + + return True + + def handle_filter_progress_dialog_cancel(self): + + self.hide_info() + self.progress_dialog = None + + self.log_filter.abort_process() + self.log_view.set_model(self.log_filter) + self.pop_view_state() + + self.set_sensitive(True) + + def handle_log_filter_process_finished(self): + + self.hide_info() + self.progress_dialog = None + + # No push_view_state here, did this in add_model_filter. + self.update_model(self.log_filter) + self.pop_view_state() + + self.actions.show_hidden_lines.props.sensitive = True + + self.set_sensitive(True) + + @action + def handle_set_base_time_action_activate(self, action): + + row = self.get_active_line() + self.column_manager.set_base_time(row[LogModelBase.COL_TIME]) + + @action + def handle_hide_log_level_action_activate(self, action): + + row = self.get_active_line() + debug_level = row[LogModelBase.COL_LEVEL] + self.add_model_filter(DebugLevelFilter(debug_level)) + + @action + def handle_hide_log_category_action_activate(self, action): + + row = self.get_active_line() + category = row[LogModelBase.COL_CATEGORY] + self.add_model_filter(CategoryFilter(category)) + + @action + def handle_hide_thread_action_activate(self, action): + + row = self.get_active_line() + thread = row[LogModelBase.COL_THREAD] + self.add_model_filter(ThreadFilter(thread)) + + @action + def handle_hide_object_action_activate(self, action): + + row = self.get_active_line() + object_ = row[LogModelBase.COL_OBJECT] + self.add_model_filter(ObjectFilter(object_)) + + @action + def handle_hide_function_action_activate(self, action): + + row = self.get_active_line() + object_ = row[LogModelBase.COL_FUNCTION] + self.add_model_filter(FunctionFilter(object_)) + + @action + def handle_hide_filename_action_activate(self, action): + + row = self.get_active_line() + filename = row[LogModelBase.COL_FILENAME] + self.add_model_filter(FilenameFilter(filename)) + + @action + def handle_hide_log_level_and_above_action_activate(self, action): + + row = self.get_active_line() + debug_level = row[LogModelBase.COL_LEVEL] + self.add_model_filter( + DebugLevelFilter(debug_level, DebugLevelFilter.this_and_above)) + + @action + def handle_show_only_log_level_action_activate(self, action): + + row = self.get_active_line() + debug_level = row[LogModelBase.COL_LEVEL] + self.add_model_filter( + DebugLevelFilter(debug_level, DebugLevelFilter.all_but_this)) + + @action + def handle_show_only_log_category_action_activate(self, action): + + row = self.get_active_line() + category = row[LogModelBase.COL_CATEGORY] + self.add_model_filter(CategoryFilter(category, True)) + + @action + def handle_show_only_thread_action_activate(self, action): + + row = self.get_active_line() + thread = row[LogModelBase.COL_THREAD] + self.add_model_filter(ThreadFilter(thread, True)) + + @action + def handle_show_only_object_action_activate(self, action): + + row = self.get_active_line() + object_ = row[LogModelBase.COL_OBJECT] + self.add_model_filter(ObjectFilter(object_, True)) + + @action + def handle_show_only_function_action_activate(self, action): + + row = self.get_active_line() + object_ = row[LogModelBase.COL_FUNCTION] + self.add_model_filter(FunctionFilter(object_, True)) + + @action + def handle_show_only_filename_action_activate(self, action): + + row = self.get_active_line() + filename = row[LogModelBase.COL_FILENAME] + self.add_model_filter(FilenameFilter(filename, True)) + + @action + def handle_show_about_action_activate(self, action): + + from GstDebugViewer import version + + dialog = self.widget_factory.make_one( + "about-dialog.ui", "about_dialog") + dialog.props.version = version + dialog.run() + dialog.destroy() + + @staticmethod + def _timestamp_cell_data_func(column, renderer, model, tree_iter): + + ts = model.get_value(tree_iter, LogModel.COL_TIME) + renderer.props.text = Data.time_args(ts) + + def _message_cell_data_func(self, column, renderer, model, tree_iter): + + offset = model.get_value(tree_iter, LogModel.COL_MESSAGE_OFFSET) + self.log_file.seek(offset) + renderer.props.text = strip_escape(self.log_file.readline().strip()) + + def set_log_file(self, filename): + + if self.log_file is not None: + for feature in self.features: + feature.handle_detach_log_file(self, self.log_file) + + if filename is None: + if self.dispatcher is not None: + self.dispatcher.cancel() + self.dispatcher = None + self.log_file = None + self.tmpfile = None + self.actions.groups["RowActions"].props.sensitive = False + else: + if self.tmpfile and filename != self.tmpfile.name: + self.tmpfile = tempfile.NamedTemporaryFile() + shutil.copyfile(filename, self.tmpfile.name) + filename = self.tmpfile.name + self.logger.debug("setting log file %r", filename) + + try: + self.setup_model(LazyLogModel()) + + self.dispatcher = Common.Data.GSourceDispatcher() + self.log_file = Data.LogFile(filename, self.dispatcher) + except EnvironmentError as exc: + try: + file_size = os.path.getsize(filename) + except EnvironmentError: + pass + else: + if file_size == 0: + # Trying to mmap an empty file results in an invalid + # argument error. + self.show_error(_("Could not open file"), + _("The selected file is empty")) + return + self.handle_environment_error(exc, filename) + return + + basename = os.path.basename(filename) + self.gtk_window.props.title = _( + "%s - GStreamer Debug Viewer") % (basename,) + + self.log_file.consumers.append(self) + self.log_file.start_loading() + + def handle_environment_error(self, exc, filename): + + self.show_error(_("Could not open file"), str(exc)) + + def show_error(self, message1, message2): + + bar = Gtk.InfoBar() + bar.props.message_type = Gtk.MessageType.ERROR + box = bar.get_content_area() + + markup = "%s %s" % (GLib.markup_escape_text(message1), + GLib.markup_escape_text(message2),) + label = Gtk.Label() + label.props.use_markup = True + label.props.label = markup + label.props.selectable = True + box.pack_start(label, False, False, 0) + + self.show_info(bar) + + def handle_load_started(self): + + self.logger.debug("load has started") + + self.progress_dialog = ProgressDialog(self, _("Loading log file")) + self.show_info(self.progress_dialog.widget) + self.progress_dialog.handle_cancel = self.handle_load_progress_dialog_cancel + self.update_progress_id = GObject.timeout_add( + 250, self.update_load_progress) + + self.set_sensitive(False) + + def handle_load_progress_dialog_cancel(self): + + self.actions.cancel_load.activate() + + def update_load_progress(self): + + if self.progress_dialog is None: + self.logger.debug( + "progress dialog is gone, removing progress update timeout") + self.update_progress_id = None + return False + + progress = self.log_file.get_load_progress() + self.progress_dialog.update(progress) + + return True + + def handle_load_finished(self): + + self.logger.debug("load has finshed") + + self.hide_info() + self.progress_dialog = None + + self.log_model.set_log(self.log_file) + self.log_filter.reset() + + self.actions.reload_file.props.sensitive = True + self.actions.groups["RowActions"].props.sensitive = True + self.actions.show_hidden_lines.props.sensitive = False + + self.set_sensitive(True) + + if len(self.log_model) == 0: + self.show_error( + _("The file does not contain any parsable lines."), + _("It is not a GStreamer log file.")) + + def idle_set(): + self.logger.debug("idle trigger after load finished") + self.log_view.set_model(self.log_filter) + + self.line_view.handle_attach_log_file(self) + for feature in self.features: + feature.handle_attach_log_file(self, self.log_file) + if len(self.log_filter): + sel = self.log_view.get_selection() + sel.select_path((0,)) + return False + + GObject.idle_add(idle_set) diff --git a/debug-viewer/GstDebugViewer/Main.py b/debug-viewer/GstDebugViewer/Main.py new file mode 100644 index 0000000000..009bd89028 --- /dev/null +++ b/debug-viewer/GstDebugViewer/Main.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Debug Viewer - View and analyze GStreamer debug log files +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer Debug Viewer Main module.""" + +import sys +import optparse +from gettext import gettext as _, ngettext + +from gi.repository import GLib + +from GstDebugViewer import GUI +import GstDebugViewer.Common.Main +Common = GstDebugViewer.Common + +GETTEXT_DOMAIN = "gst-debug-viewer" + + +def main_version(opt, value, parser, *args, **kwargs): + + from GstDebugViewer import version + + print("GStreamer Debug Viewer %s" % (version,)) + sys.exit(0) + + +class Paths (Common.Main.PathsProgramBase): + + program_name = "gst-debug-viewer" + + +def main(): + parser = optparse.OptionParser( + _("%prog [OPTION...] [FILENAME]"), + description=_("Display and analyze GStreamer debug log files")) + parser.add_option("--version", "-v", + action="callback", + dest="version", + callback=main_version, + help=_("Display version and exit")) + + Common.Main.main(main_function=GUI.main, + option_parser=parser, + gettext_domain=GETTEXT_DOMAIN, + paths=Paths) diff --git a/debug-viewer/GstDebugViewer/Plugins/FindBar.py b/debug-viewer/GstDebugViewer/Plugins/FindBar.py new file mode 100644 index 0000000000..151773fceb --- /dev/null +++ b/debug-viewer/GstDebugViewer/Plugins/FindBar.py @@ -0,0 +1,497 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Debug Viewer - View and analyze GStreamer debug log files +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer Debug Viewer timeline widget plugin.""" + +import logging + +from GstDebugViewer import Common, Data, GUI +from GstDebugViewer.Plugins import FeatureBase, PluginBase, _N + +from gettext import gettext as _ +from gi.repository import GObject, GLib +from gi.repository import Gtk + + +class SearchOperation (object): + + def __init__(self, model, search_text, search_forward=True, start_position=None): + + self.model = model + if isinstance(search_text, str): + self.search_text = search_text.encode('utf8') + else: + self.search_text = search_text + self.search_forward = search_forward + self.start_position = start_position + + col_id = GUI.models.LogModelBase.COL_MESSAGE + len_search_text = len(self.search_text) + + def match_func(model_row): + + message = model_row[col_id] + if self.search_text in message: + ranges = [] + start = 0 + while True: + pos = message.find(self.search_text, start) + if pos == -1: + break + ranges.append((pos, pos + len_search_text,)) + start = pos + len_search_text + return ranges + else: + return () + + self.match_func = match_func + + +class SearchSentinel (object): + + def __init__(self): + + self.dispatcher = Common.Data.GSourceDispatcher() + self.cancelled = False + + def run_for(self, operation): + + self.dispatcher.cancel() + self.dispatcher(self.__process(operation)) + self.cancelled = False + + def abort(self): + + self.dispatcher.cancel() + self.cancelled = True + + def __process(self, operation): + + model = operation.model + + if operation.start_position is not None: + start_pos = operation.start_position + elif operation.search_forward: + start_pos = 0 + else: + start_pos = len(model) - 1 + + start_iter = model.iter_nth_child(None, start_pos) + + match_func = operation.match_func + if operation.search_forward: + iter_next = model.iter_next + else: + # FIXME: This is really ugly. + nth_child = model.iter_nth_child + + def iter_next_(): + for i in range(start_pos, -1, -1): + yield nth_child(None, i) + yield None + it_ = iter_next_() + + def iter_next(it): + return it_.__next__() + + YIELD_LIMIT = 1000 + i = YIELD_LIMIT + tree_iter = start_iter + while tree_iter and not self.cancelled: + i -= 1 + if i == 0: + yield True + i = YIELD_LIMIT + row = model[tree_iter] + if match_func(row): + self.handle_match_found(model, tree_iter) + tree_iter = iter_next(tree_iter) + + if not self.cancelled: + self.handle_search_complete() + yield False + + def handle_match_found(self, model, tree_iter): + + pass + + def handle_search_complete(self): + + pass + + +class FindBarWidget (Gtk.HBox): + + __status = {"no-match-found": _N("No match found"), + "searching": _N("Searching...")} + + def __init__(self, action_group): + + GObject.GObject.__init__(self) + + label = Gtk.Label(label=_("Find:")) + self.pack_start(label, False, False, 2) + + self.entry = Gtk.Entry() + self.pack_start(self.entry, True, True, 0) + + prev_action = action_group.get_action("goto-previous-search-result") + prev_button = Gtk.Button() + prev_button.set_related_action(prev_action) + self.pack_start(prev_button, False, False, 0) + + next_action = action_group.get_action("goto-next-search-result") + next_button = Gtk.Button() + next_button.set_related_action(next_action) + self.pack_start(next_button, False, False, 0) + + self.status_label = Gtk.Label() + self.status_label.props.xalign = 0. + self.status_label.props.use_markup = True + self.pack_start(self.status_label, False, False, 6) + self.__compute_status_size() + self.status_label.connect("notify::style", self.__handle_notify_style) + + self.show_all() + + def __compute_status_size(self): + + label = self.status_label + old_markup = label.props.label + label.set_size_request(-1, -1) + max_width = 0 + try: + for status in self.__status.values(): + self.__set_status(_(status)) + req = label.size_request() + max_width = max(max_width, req.width) + label.set_size_request(max_width, -1) + finally: + label.props.label = old_markup + + def __handle_notify_style(self, *a, **kw): + + self.__compute_status_size() + + def __set_status(self, text): + + markup = "%s" % (GLib.markup_escape_text(text),) + + self.status_label.props.label = markup + + def status_no_match_found(self): + + self.__set_status(_(self.__status["no-match-found"])) + + def status_searching(self): + + self.__set_status(_(self.__status["searching"])) + + def clear_status(self): + + self.__set_status("") + + +class FindBarFeature (FeatureBase): + + def __init__(self, app): + + FeatureBase.__init__(self, app) + + self.logger = logging.getLogger("ui.findbar") + + self.action_group = Gtk.ActionGroup("FindBarActions") + self.action_group.add_toggle_actions([("show-find-bar", + None, + _("Find Bar"), + "F")]) + self.action_group.add_actions([("goto-next-search-result", + None, _("Goto Next Match"), + "G"), + ("goto-previous-search-result", + None, _("Goto Previous Match"), + "G")]) + + self.bar = None + self.operation = None + self.search_state = None + self.next_match = None + self.prev_match = None + self.scroll_match = False + + self.sentinel = SearchSentinel() + self.sentinel.handle_match_found = self.handle_match_found + self.sentinel.handle_search_complete = self.handle_search_complete + + def scroll_view_to_line(self, line_index): + + view = self.log_view + + path = Gtk.TreePath((line_index,)) + + start_path, end_path = view.get_visible_range() + + if path >= start_path and path <= end_path: + self.logger.debug( + "line index %i already visible, not scrolling", line_index) + return + + self.logger.debug("scrolling to line_index %i", line_index) + view.scroll_to_cell(path, use_align=True, row_align=.5) + + def handle_attach_window(self, window): + + self.window = window + + ui = window.ui_manager + + ui.insert_action_group(self.action_group, 0) + + self.log_view = window.log_view + + self.merge_id = ui.new_merge_id() + for name, action_name in [("ViewFindBar", "show-find-bar",), + ("ViewNextResult", + "goto-next-search-result",), + ("ViewPrevResult", "goto-previous-search-result",)]: + ui.add_ui(self.merge_id, "/menubar/ViewMenu/ViewMenuAdditions", + name, action_name, Gtk.UIManagerItemType.MENUITEM, False) + + box = window.widgets.vbox_view + self.bar = FindBarWidget(self.action_group) + box.pack_end(self.bar, False, False, 0) + self.bar.hide() + + action = self.action_group.get_action("show-find-bar") + handler = self.handle_show_find_bar_action_toggled + action.connect("toggled", handler) + + action = self.action_group.get_action("goto-previous-search-result") + handler = self.handle_goto_previous_search_result_action_activate + action.props.sensitive = False + action.connect("activate", handler) + + action = self.action_group.get_action("goto-next-search-result") + handler = self.handle_goto_next_search_result_action_activate + action.props.sensitive = False + action.connect("activate", handler) + + self.bar.entry.connect("changed", self.handle_entry_changed) + + def handle_detach_window(self, window): + + self.window = None + + window.ui_manager.remove_ui(self.merge_id) + self.merge_id = None + + def handle_show_find_bar_action_toggled(self, action): + + if action.props.active: + self.bar.show() + self.bar.entry.grab_focus() + self.update_search() + else: + try: + column = self.window.column_manager.find_item( + name="message") + del column.highlighters[self] + except KeyError: + pass + self.bar.clear_status() + self.bar.hide() + for action_name in ["goto-next-search-result", + "goto-previous-search-result"]: + self.action_group.get_action( + action_name).props.sensitive = False + + def handle_goto_previous_search_result_action_activate(self, action): + + if self.prev_match is None: + self.logger.warning("inconsistent action sensitivity") + return + + self.scroll_view_to_line(self.prev_match) + self.prev_match = None + + start_path = self.log_view.get_visible_range()[0] + new_position = start_path[0] - 1 + self.start_search_operation(start_position=new_position, + forward=False) + + # FIXME + + def handle_goto_next_search_result_action_activate(self, action): + + if self.next_match is None: + self.logger.warning("inconsistent action sensitivity") + return + + self.scroll_view_to_line(self.next_match) + self.next_match = None + + end_path = self.log_view.get_visible_range()[1] + new_position = end_path[0] + 1 + self.start_search_operation(start_position=new_position, + forward=True) + # FIXME: Finish. + + # model = self.log_view.get_model () + + # start_path, end_path = self.log_view.get_visible_range () + # start_index, end_index = start_path[0], end_path[0] + + # for line_index in self.matches: + # if line_index > end_index: + # break + # else: + # return + + # self.scroll_view_to_line (line_index) + + def handle_entry_changed(self, entry): + + self.update_search() + + def update_search(self): + + model = self.log_view.get_model() + search_text = self.bar.entry.props.text + column = self.window.column_manager.find_item(name="message") + if search_text == "": + self.logger.debug("search string set to '', aborting search") + self.search_state = None + self.next_match = None + self.prev_match = None + self.update_sensitivity() + self.sentinel.abort() + try: + del column.highlighters[self] + except KeyError: + pass + else: + self.logger.debug("starting search for %r", search_text) + self.next_match = None + self.prev_match = None + self.update_sensitivity() + self.scroll_match = True + + start_path = self.log_view.get_visible_range()[0] + self.start_search_operation( + search_text, start_position=start_path[0]) + self.bar.status_searching() + column.highlighters[self] = self.operation.match_func + + self.window.update_view() + + def update_sensitivity(self): + + for name, value in (("goto-next-search-result", self.next_match,), + ("goto-previous-search-result", self.prev_match,),): + action = self.action_group.get_action(name) + action.props.sensitive = (value is not None) + + def start_search_operation(self, search_text=None, forward=True, start_position=None): + + model = self.log_view.get_model() + + if forward: + self.search_state = "search-forward" + if start_position is None: + start_position = 0 + else: + self.search_state = "search-backward" + if start_position is None: + start_position = len(model) - 1 + + if search_text is None: + operation = self.operation + if operation is None: + raise ValueError( + "search_text not given but have no previous search operation") + search_text = operation.search_text + + self.operation = SearchOperation(model, search_text, + start_position=start_position, + search_forward=forward) + self.sentinel.run_for(self.operation) + + def handle_match_found(self, model, tree_iter): + + if self.search_state not in ("search-forward", "search-backward",): + self.logger.warning( + "inconsistent search state %r", self.search_state) + return + + line_index = model.get_path(tree_iter)[0] + forward_search = (self.search_state == "search-forward") + + if forward_search: + self.logger.debug("forward search for %r matches line %i", + self.operation.search_text, line_index) + else: + self.logger.debug("backward search for %r matches line %i", + self.operation.search_text, line_index) + + self.sentinel.abort() + + if self.scroll_match: + self.logger.debug("scrolling to matching line") + self.scroll_view_to_line(line_index) + # Now search for the next one: + self.scroll_match = False + # FIXME: Start with first line that is outside of the visible + # range. + self.start_search_operation(start_position=line_index + 1, + forward=forward_search) + else: + if forward_search: + self.next_match = line_index + + self.search_state = "search-backward" + self.start_search_operation(forward=False, + start_position=line_index - 1) + else: + self.prev_match = line_index + self.update_sensitivity() + self.bar.clear_status() + + def handle_search_complete(self): + + if self.search_state == "search-forward": + self.logger.debug("forward search for %r reached last line", + self.operation.search_text) + self.next_match = None + elif self.search_state == "search-backward": + self.logger.debug("backward search for %r reached first line", + self.operation.search_text) + self.prev_match = None + else: + self.logger.warning("inconsistent search state %r", + self.search_state) + return + + self.update_sensitivity() + if self.prev_match is None and self.next_match is None: + self.bar.status_no_match_found() + + +class Plugin (PluginBase): + + features = (FindBarFeature,) diff --git a/debug-viewer/GstDebugViewer/Plugins/Timeline.py b/debug-viewer/GstDebugViewer/Plugins/Timeline.py new file mode 100644 index 0000000000..6d9f7cd5b1 --- /dev/null +++ b/debug-viewer/GstDebugViewer/Plugins/Timeline.py @@ -0,0 +1,1099 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Debug Viewer - View and analyze GStreamer debug log files +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer Debug Viewer timeline widget plugin.""" + +import logging + +from GstDebugViewer import Common, Data +from GstDebugViewer.GUI.colors import LevelColorThemeTango, ThreadColorThemeTango +from GstDebugViewer.Plugins import FeatureBase, PluginBase + +from gettext import gettext as _ +from gi.repository import GObject +from gi.repository import Gtk +from gi.repository import Gdk +import cairo + + +def iter_model_reversed(model): + + count = model.iter_n_children(None) + for i in range(count - 1, 0, -1): + yield model[i] + + +class LineFrequencySentinel (object): + + def __init__(self, model): + + self.model = model + self.clear() + + def clear(self): + + self.data = None + self.n_partitions = None + self.partitions = None + self.step = None + self.ts_range = None + + def _search_ts(self, target_ts, first_index, last_index): + + model_get = self.model.get_value + model_iter_nth_child = self.model.iter_nth_child + col_id = self.model.COL_TIME + + # TODO: Rewrite using a lightweight view object + bisect. + + while True: + middle = (last_index - first_index) // 2 + first_index + if middle == first_index: + return first_index + ts = model_get(model_iter_nth_child(None, middle), col_id) + if ts < target_ts: + first_index = middle + elif ts > target_ts: + last_index = middle + else: + return middle + + def run_for(self, n): + + if n == 0: + raise ValueError("illegal value for n") + + self.n_partitions = n + + def process(self): + + model = self.model + result = [] + partitions = [] + + first_ts = None + for row in self.model: + first_ts = row[model.COL_TIME] + if first_ts is not None: + break + + if first_ts is None: + return + + last_ts = None + i = 0 + UNPARSABLE_LIMIT = 500 + for row in iter_model_reversed(self.model): + last_ts = row[model.COL_TIME] + # FIXME: We ignore 0 here (unparsable lines!), this should be + # handled differently! + i += 1 + if i == UNPARSABLE_LIMIT: + break + if last_ts: + last_index = row.path[0] + break + + if last_ts is None or last_ts < first_ts: + return + + step = int(float(last_ts - first_ts) / float(self.n_partitions)) + + YIELD_LIMIT = 100 + limit = YIELD_LIMIT + + first_index = 0 + target_ts = first_ts + step + old_found = 0 + while target_ts < last_ts: + limit -= 1 + if limit == 0: + limit = YIELD_LIMIT + yield True + found = self._search_ts(target_ts, first_index, last_index) + result.append(found - old_found) + partitions.append(found) + old_found = found + first_index = found + target_ts += step + + if step == 0: + result = [] + partitions = [] + + self.step = step + self.data = result + self.partitions = partitions + self.ts_range = (first_ts, last_ts,) + + +class LevelDistributionSentinel (object): + + def __init__(self, freq_sentinel, model): + + self.freq_sentinel = freq_sentinel + self.model = model + self.data = [] + + def clear(self): + + del self.data[:] + + def process(self): + + MAX_LEVELS = 9 + YIELD_LIMIT = 10000 + y = YIELD_LIMIT + + model_get = self.model.get_value + model_next = self.model.iter_next + id_time = self.model.COL_TIME + id_level = self.model.COL_LEVEL + del self.data[:] + data = self.data + i = 0 + partitions_i = 0 + partitions = self.freq_sentinel.partitions + counts = [0] * MAX_LEVELS + tree_iter = self.model.get_iter_first() + + if not partitions: + return + + level_index = 0 + level_iter = None + + finished = False + while tree_iter: + y -= 1 + if y == 0: + y = YIELD_LIMIT + yield True + if level_iter is None: + stop_index = level_index + 512 + levels = self.model.get_value_range(id_level, + level_index, stop_index) + level_index = stop_index + level_iter = iter(levels) + try: + level = level_iter.__next__() + except StopIteration: + level_iter = None + continue + while i > partitions[partitions_i]: + data.append(tuple(counts)) + counts = [0] * MAX_LEVELS + partitions_i += 1 + if partitions_i == len(partitions): + finished = True + break + if finished: + break + counts[level] += 1 + i += 1 + + # Now handle the last one: + data.append(tuple(counts)) + + yield False + + +class UpdateProcess (object): + + def __init__(self, freq_sentinel, dist_sentinel): + + self.freq_sentinel = freq_sentinel + self.dist_sentinel = dist_sentinel + self.is_running = False + self.dispatcher = Common.Data.GSourceDispatcher() + + def __process(self): + + if self.freq_sentinel is None or self.dist_sentinel is None: + return + + self.is_running = True + + for x in self.freq_sentinel.process(): + yield True + + self.handle_sentinel_finished(self.freq_sentinel) + + for x in self.dist_sentinel.process(): + yield True + self.handle_sentinel_progress(self.dist_sentinel) + + self.is_running = False + + self.handle_sentinel_finished(self.dist_sentinel) + self.handle_process_finished() + + yield False + + def run(self): + + if self.is_running: + return + + self.dispatcher(self.__process()) + + def abort(self): + + if not self.is_running: + return + + self.dispatcher.cancel() + self.is_running = False + + def handle_sentinel_progress(self, sentinel): + + pass + + def handle_sentinel_finished(self, sentinel): + + pass + + def handle_process_finished(self): + + pass + + +class VerticalTimelineWidget (Gtk.DrawingArea): + + __gtype_name__ = "GstDebugViewerVerticalTimelineWidget" + + def __init__(self, log_view): + + GObject.GObject.__init__(self) + + self.logger = logging.getLogger("ui.vtimeline") + + self.log_view = log_view + self.theme = ThreadColorThemeTango() + self.params = None + self.thread_colors = {} + self.next_thread_color = 0 + + try: + self.set_tooltip_text(_("Vertical timeline\n" + "Different colors represent different threads")) + except AttributeError: + # Compatibility. + pass + + def do_draw(self, ctx): + + alloc = self.get_allocation() + x = alloc.x + y = alloc.y + w = alloc.width + h = alloc.height + + # White background rectangle. + ctx.set_line_width(0.) + ctx.rectangle(0, 0, w, h) + ctx.set_source_rgb(1., 1., 1.) + ctx.fill() + ctx.new_path() + + if self.params is None: + self.__update_params() + + if self.params is None: + return + + first_y, cell_height, data = self.params + if len(data) < 2: + return + first_ts, last_ts = data[0][0], data[-1][0] + ts_range = last_ts - first_ts + if ts_range == 0: + return + + ctx.set_line_width(1.) + ctx.set_source_rgb(0., 0., 0.) + + half_height = cell_height // 2 - .5 + quarter_height = cell_height // 4 - .5 + first_y += half_height + for i, i_data in enumerate(data): + ts, thread = i_data + if thread in self.thread_colors: + ctx.set_source_rgb(*self.thread_colors[thread]) + else: + self.next_thread_color += 1 + if self.next_thread_color == len(self.theme.colors): + self.next_thread_color = 0 + color = self.theme.colors[ + self.next_thread_color][0].float_tuple() + self.thread_colors[thread] = color + ctx.set_source_rgb(*color) + ts_fraction = float(ts - first_ts) / ts_range + ts_offset = ts_fraction * h + row_offset = first_y + i * cell_height + ctx.move_to(-.5, ts_offset) + ctx.line_to(half_height, ts_offset) + ctx.line_to(w - quarter_height, row_offset) + ctx.stroke() + ctx.line_to(w - quarter_height, row_offset) + ctx.line_to(w + .5, row_offset - half_height) + ctx.line_to(w + .5, row_offset + half_height) + ctx.fill() + return True + + def do_configure_event(self, event): + + self.params = None + self.queue_draw() + + return False + + def do_get_preferred_width(self): + + return 64, 64 # FIXME + + def clear(self): + + self.params = None + self.thread_colors.clear() + self.next_thread_color = 0 + self.queue_draw() + + def __update_params(self): + + # FIXME: Ideally we should take the vertical position difference of the + # view into account (which is 0 with the current UI layout). + + view = self.log_view + model = view.get_model() + visible_range = view.get_visible_range() + if visible_range is None: + return + start_path, end_path = visible_range + + if not start_path or not end_path: + return + + column = view.get_column(0) + bg_rect = view.get_background_area(start_path, column) + cell_height = bg_rect.height + cell_rect = view.get_cell_area(start_path, column) + try: + first_y = view.convert_bin_window_to_widget_coords( + cell_rect.x, cell_rect.y)[1] + except (AttributeError, SystemError,): + # AttributeError is with PyGTK before 2.12. SystemError is raised + # with PyGTK 2.12.0, pygtk bug #479012. + first_y = cell_rect.y % cell_height + + global _warn_tree_view_coords + try: + _warn_tree_view_coords + except NameError: + self.logger.warning("tree view coordinate conversion method " + "not available, using aproximate offset") + # Only warn once: + _warn_tree_view_coords = True + + data = [] + tree_iter = model.get_iter(start_path) + if tree_iter is None: + return + while model.get_path(tree_iter) != end_path: + data.append( + model.get(tree_iter, model.COL_TIME, model.COL_THREAD)) + tree_iter = model.iter_next(tree_iter) + + self.params = (first_y, cell_height, data,) + + def update(self): + + self.params = None + self.queue_draw() + + +class TimelineWidget (Gtk.DrawingArea): + + __gtype_name__ = "GstDebugViewerTimelineWidget" + + __gsignals__ = {"change-position": (GObject.SignalFlags.RUN_LAST, + None, + (GObject.TYPE_INT,),)} + + def __init__(self): + + GObject.GObject.__init__(self) + + self.logger = logging.getLogger("ui.timeline") + + self.add_events(Gdk.EventMask.BUTTON1_MOTION_MASK | + Gdk.EventMask.BUTTON_PRESS_MASK | + Gdk.EventMask.BUTTON_RELEASE_MASK) + + self.process = UpdateProcess(None, None) + self.process.handle_sentinel_progress = self.__handle_sentinel_progress + self.process.handle_sentinel_finished = self.__handle_sentinel_finished + + self.model = None + self.__offscreen = None + self.__offscreen_size = (0, 0) + self.__offscreen_dirty = (0, 0) + + self.__position_ts_range = None + + try: + self.set_tooltip_text(_("Log event histogram\n" + "Different colors represent different log-levels")) + except AttributeError: + # Compatibility. + pass + + def __handle_sentinel_progress(self, sentinel): + + if sentinel == self.process.dist_sentinel: + old_progress = self.__dist_sentinel_progress + new_progress = len(sentinel.data) + if new_progress - old_progress >= 32: + self.__invalidate_offscreen(old_progress, new_progress) + self.__dist_sentinel_progress = new_progress + + def __handle_sentinel_finished(self, sentinel): + + if sentinel == self.process.freq_sentinel: + self.__invalidate_offscreen(0, -1) + else: + self.__invalidate_offscreen(self.__dist_sentinel_progress, -1) + + def __ensure_offscreen(self): + + alloc = self.get_allocation() + if self.__offscreen_size == (alloc.width, alloc.height): + return + + self.__offscreen = cairo.ImageSurface( + cairo.FORMAT_ARGB32, alloc.width, alloc.height) + self.__offscreen_size = (alloc.width, alloc.height) + self.__offscreen_dirty = (0, alloc.width) + if not self.__offscreen: + self.__offscreen_size = (0, 0) + raise ValueError("could not obtain offscreen image surface") + + def __invalidate_offscreen(self, start, stop): + + alloc = self.get_allocation() + if stop < 0: + stop += alloc.width + + dirty_start, dirty_stop = self.__offscreen_dirty + if dirty_start != dirty_stop: + dirty_start = min(dirty_start, start) + dirty_stop = max(dirty_stop, stop) + else: + dirty_start = start + dirty_stop = stop + self.__offscreen_dirty = (dirty_start, dirty_stop) + + # Just like in __draw_offscreen. FIXME: Need this in one place! + start -= 8 + stop += 8 + self.queue_draw_area(start, 0, stop - start, alloc.height) + + def __draw_from_offscreen(self, ctx): + + if not self.props.visible: + return + + alloc = self.get_allocation() + offscreen_width, offscreen_height = self.__offscreen_size + rect = Gdk.Rectangle() # TODO: damage region + rect.x, rect.y, rect.width, rect.height = 0, 0, alloc.width, alloc.height + + # Fill the background (where the offscreen pixmap doesn't fit) with + # white. This happens after enlarging the window, until all sentinels + # have finished running. + if offscreen_width < alloc.width or offscreen_height < alloc.height: + ctx.rectangle(rect.x, rect.y, rect.width, rect.height) + ctx.clip() + + if offscreen_width < alloc.width: + ctx.rectangle( + offscreen_width, 0, alloc.width, offscreen_height) + if offscreen_height < alloc.height: + ctx.new_path() + ctx.rectangle(0, offscreen_height, alloc.width, alloc.height) + + ctx.set_line_width(0.) + ctx.set_source_rgb(1., 1., 1.) + ctx.fill() + + ctx.set_source_surface(self.__offscreen) + ctx.rectangle(rect.x, rect.y, rect.width, rect.height) + ctx.paint() + + self.__draw_position(ctx, clip=rect) + + def update(self, model): + + self.clear() + self.model = model + + if model is not None: + self.__dist_sentinel_progress = 0 + self.process.freq_sentinel = LineFrequencySentinel(model) + self.process.dist_sentinel = LevelDistributionSentinel( + self.process.freq_sentinel, model) + width = self.get_allocation().width + self.process.freq_sentinel.run_for(width) + self.process.run() + + def clear(self): + + self.model = None + self.process.abort() + self.process.freq_sentinel = None + self.process.dist_sentinel = None + self.__invalidate_offscreen(0, -1) + + def update_position(self, start_ts, end_ts): + + if not self.process.freq_sentinel: + return + + if not self.process.freq_sentinel.data: + return + + alloc = self.get_allocation() + + # Queue old position rectangle for redraw: + if self.__position_ts_range is not None: + start, stop = self.ts_range_to_position(*self.__position_ts_range) + self.queue_draw_area(start - 1, 0, stop - start + 2, alloc.height) + # And the new one: + start, stop = self.ts_range_to_position(start_ts, end_ts) + self.queue_draw_area(start - 1, 0, stop - start + 2, alloc.height) + + self.__position_ts_range = (start_ts, end_ts,) + + def find_indicative_time_step(self): + + MINIMUM_PIXEL_STEP = 32 + time_per_pixel = self.process.freq_sentinel.step + return 32 # FIXME use self.freq_sentinel.step and len (self.process.freq_sentinel.data) + + def __draw_offscreen(self): + + dirty_start, dirty_stop = self.__offscreen_dirty + if dirty_start == dirty_stop: + return + + self.__offscreen_dirty = (0, 0) + width, height = self.__offscreen_size + + ctx = cairo.Context(self.__offscreen) + + # Indicator (triangle) size is 8, so we need to draw surrounding areas + # a bit: + dirty_start -= 8 + dirty_stop += 8 + dirty_start = max(dirty_start, 0) + dirty_stop = min(dirty_stop, width) + + ctx.rectangle(dirty_start, 0., dirty_stop, height) + ctx.clip() + + # White background rectangle. + ctx.set_line_width(0.) + ctx.rectangle(0, 0, width, height) + ctx.set_source_rgb(1., 1., 1.) + ctx.fill() + ctx.new_path() + + # Horizontal reference lines. + ctx.set_line_width(1.) + ctx.set_source_rgb(.95, .95, .95) + for i in range(height // 16): + y = i * 16 - .5 + ctx.move_to(0, y) + ctx.line_to(width, y) + ctx.stroke() + + if self.process.freq_sentinel is None: + return + + # Vertical reference lines. + pixel_step = self.find_indicative_time_step() + ctx.set_source_rgb(.9, .9, .9) + start = dirty_start - dirty_start % pixel_step + for x in range(start + pixel_step, dirty_stop, pixel_step): + ctx.move_to(x - .5, 0) + ctx.line_to(x - .5, height) + ctx.stroke() + + if not self.process.freq_sentinel.data: + self.logger.debug("frequency sentinel has no data yet") + return + + ctx.translate(dirty_start, 0.) + + maximum = max(self.process.freq_sentinel.data) + + ctx.set_source_rgb(0., 0., 0.) + data = self.process.freq_sentinel.data[dirty_start:dirty_stop] + self.__draw_graph(ctx, height, maximum, data) + + if not self.process.dist_sentinel.data: + self.logger.debug("level distribution sentinel has no data yet") + return + + colors = LevelColorThemeTango().colors + dist_data = self.process.dist_sentinel.data[dirty_start:dirty_stop] + + def cumulative_level_counts(*levels): + for level_counts in dist_data: + yield sum((level_counts[level] for level in levels)) + + level = Data.debug_level_info + levels_prev = (Data.debug_level_trace, + Data.debug_level_fixme, + Data.debug_level_log, + Data.debug_level_debug,) + ctx.set_source_rgb(*(colors[level][1].float_tuple())) + self.__draw_graph(ctx, height, maximum, + list(cumulative_level_counts(level, *levels_prev))) + + level = Data.debug_level_debug + levels_prev = (Data.debug_level_trace, + Data.debug_level_fixme, + Data.debug_level_log,) + ctx.set_source_rgb(*(colors[level][1].float_tuple())) + self.__draw_graph(ctx, height, maximum, + list(cumulative_level_counts(level, *levels_prev))) + + level = Data.debug_level_log + levels_prev = (Data.debug_level_trace, Data.debug_level_fixme,) + ctx.set_source_rgb(*(colors[level][1].float_tuple())) + self.__draw_graph(ctx, height, maximum, + list(cumulative_level_counts(level, *levels_prev))) + + level = Data.debug_level_fixme + levels_prev = (Data.debug_level_trace,) + ctx.set_source_rgb(*(colors[level][1].float_tuple())) + self.__draw_graph(ctx, height, maximum, + list(cumulative_level_counts(level, *levels_prev))) + + level = Data.debug_level_trace + ctx.set_source_rgb(*(colors[level][1].float_tuple())) + self.__draw_graph(ctx, height, maximum, [ + counts[level] for counts in dist_data]) + + # Draw error and warning triangle indicators: + + def triangle(ctx, size=8): + ctx.move_to(-size // 2, 0) + ctx.line_to((size + 1) // 2, 0) + ctx.line_to(0, size / 1.41) + ctx.close_path() + + for level in (Data.debug_level_warning, Data.debug_level_error,): + ctx.set_source_rgb(*(colors[level][1].float_tuple())) + for i, counts in enumerate(dist_data): + if counts[level] == 0: + continue + ctx.translate(i, 0.) + triangle(ctx) + ctx.fill() + ctx.translate(-i, 0.) + + def __draw_graph(self, ctx, height, maximum, data): + + if not data: + return + + if maximum: + heights = [height * float(d) / maximum for d in data] + else: + heights = [0. for d in data] + + ctx.move_to(0, height) + for i in range(len(heights)): + ctx.line_to(i - .5, height - heights[i] + .5) + + ctx.line_to(i, height) + ctx.close_path() + + ctx.fill() + + def __have_position(self): + + if ((self.process is not None) and + (self.process.freq_sentinel is not None) and + (self.process.freq_sentinel.ts_range is not None)): + return True + else: + return False + + def ts_range_to_position(self, start_ts, end_ts): + + if not self.__have_position(): + return (0, 0) + + first_ts, last_ts = self.process.freq_sentinel.ts_range + step = self.process.freq_sentinel.step + if step == 0: + return (0, 0) + + position1 = int(float(start_ts - first_ts) / step) + position2 = int(float(end_ts - first_ts) / step) + + return (position1, position2) + + def __draw_position(self, ctx, clip=None): + + if not self.__have_position() or self.__position_ts_range is None: + if not self.__have_position(): + self.logger.debug("have no positions") + else: + self.logger.debug("have no positions_ts_range") + return + + start_ts, end_ts = self.__position_ts_range + position1, position2 = self.ts_range_to_position(start_ts, end_ts) + + if clip: + if clip.x + clip.width < position1 - 1 or clip.x > position2 + 1: + self.logger.debug( + "outside of clip range: %d + %d, pos: %d, %d", clip.x, clip.width, position1, position2) + return + ctx.rectangle(clip.x, clip.y, clip.width, clip.height) + ctx.clip() + + height = self.get_allocation().height + + line_width = position2 - position1 + if line_width <= 1: + ctx.set_source_rgb(1., 0., 0.) + ctx.set_line_width(1.) + ctx.move_to(position1 + .5, 0) + ctx.line_to(position1 + .5, height) + ctx.stroke() + else: + ctx.set_source_rgba(1., 0., 0., .5) + ctx.rectangle(position1, 0, line_width, height) + ctx.fill() + + def do_draw(self, cr): + + self.__ensure_offscreen() + self.__draw_offscreen() + self.__draw_from_offscreen(cr) + + return True + + def do_configure_event(self, event): + + self.logger.debug("widget size configured to %ix%i", + event.width, event.height) + + if event.width < 16: + return False + + self.update(self.model) + + return False + + def do_get_preferred_height(self): + + return 64, 64 # FIXME: + + def do_button_press_event(self, event): + + if event.button != 1: + return False + + # TODO: Check if clicked inside a warning/error indicator triangle and + # navigate there. + + if not self.has_grab(): + self.grab_add() + self.props.has_tooltip = False + + pos = int(event.x) + self.emit("change-position", pos) + return True + + def do_button_release_event(self, event): + + if event.button != 1: + return False + + if self.has_grab(): + self.grab_remove() + self.props.has_tooltip = True + + return True + + def do_motion_notify_event(self, event): + + if event.get_state() & Gdk.ModifierType.BUTTON1_MASK: + self.emit("change-position", int(event.x)) + Gdk.event_request_motions(event) + return True + else: + self._handle_motion(event.x, event.y) + Gdk.event_request_motions(event) + return False + + def _handle_motion(self, x, y): + + # TODO: Prelight warning and error indicator triangles. + + pass + + +class AttachedWindow (object): + + def __init__(self, feature, window): + + self.window = window + + ui = window.ui_manager + + ui.insert_action_group(feature.action_group, 0) + + self.merge_id = ui.new_merge_id() + ui.add_ui(self.merge_id, "/menubar/ViewMenu/ViewMenuAdditions", + "ViewTimeline", "show-timeline", + Gtk.UIManagerItemType.MENUITEM, False) + + ui.add_ui(self.merge_id, "/", "TimelineContextMenu", None, + Gtk.UIManagerItemType.POPUP, False) + # TODO: Make hide before/after operate on the partition that the mouse + # is pointed at instead of the currently selected line. + # ui.add_ui (self.merge_id, "/TimelineContextMenu", "TimelineHideLinesBefore", + # "hide-before-line", Gtk.UIManagerItemType.MENUITEM, False) + # ui.add_ui (self.merge_id, "/TimelineContextMenu", "TimelineHideLinesAfter", + # "hide-after-line", Gtk.UIManagerItemType.MENUITEM, False) + ui.add_ui( + self.merge_id, "/TimelineContextMenu", "TimelineShowHiddenLines", + "show-hidden-lines", Gtk.UIManagerItemType.MENUITEM, False) + + box = window.get_top_attach_point() + + self.timeline = TimelineWidget() + self.timeline.connect("change-position", + self.handle_timeline_change_position) + box.pack_start(self.timeline, False, False, 0) + self.timeline.hide() + + self.popup = ui.get_widget("/TimelineContextMenu") + Common.GUI.widget_add_popup_menu(self.timeline, self.popup) + + box = window.get_side_attach_point() + + self.vtimeline = VerticalTimelineWidget(self.window.log_view) + box.pack_start(self.vtimeline, False, False, 0) + self.vtimeline.hide() + + handler = self.handle_log_view_adjustment_value_changed + adjustment = window.widgets.log_view_scrolled_window.props.vadjustment + adjustment.connect("value-changed", handler) + + handler = self.handle_show_action_toggled + action = feature.action_group.get_action("show-timeline") + action.connect("toggled", handler) + handler(action) + + handler = self.handle_log_view_notify_model + self.notify_model_id = window.log_view.connect( + "notify::model", handler) + + self.idle_scroll_path = None + self.idle_scroll_id = None + + def detach(self, feature): + + self.window.log_view.disconnect(self.notify_model_id) + self.notify_model_id = None + + self.window.ui_manager.remove_ui(self.merge_id) + self.merge_id = None + + self.window.ui_manager.remove_action_group(feature.action_group) + + self.timeline.destroy() + self.timeline = None + + self.idle_scroll_path = None + if self.idle_scroll_id is not None: + GObject.source_remove(self.idle_scroll_id) + self.idle_scroll_id = None + + def handle_detach_log_file(self, log_file): + + self.timeline.clear() + self.vtimeline.clear() + + def handle_log_view_notify_model(self, view, gparam): + + model = view.get_model() + + if model is None: + self.timeline.clear() + self.vtimeline.clear() + return + + self.timeline.update(model) + + # Need to dispatch these idly with a low priority to avoid triggering a + # warning in treeview.get_visible_range: + def idle_update(): + self.update_timeline_position() + self.vtimeline.update() + return False + GObject.idle_add(idle_update, priority=GObject.PRIORITY_LOW) + + def handle_log_view_adjustment_value_changed(self, adj): + + # FIXME: If not visible, disconnect this handler! + if not self.timeline.props.visible: + return + + self.update_timeline_position() + self.vtimeline.update() + + def update_timeline_position(self): + + visible_range = self.window.get_range() + if visible_range is None: + return + ts1, ts2 = visible_range + self.timeline.update_position(ts1, ts2) + + def handle_show_action_toggled(self, action): + + show = action.props.active + + if show: + self.timeline.show() + self.vtimeline.show() + else: + self.timeline.hide() + self.vtimeline.hide() + + def handle_timeline_change_position(self, widget, pos): + + self.goto_time_position(pos) + + def goto_time_position(self, pos): + + if not self.timeline.process.freq_sentinel: + return True + + data = self.timeline.process.freq_sentinel.data + if not data: + return True + + if pos < 0: + pos = 0 + elif pos >= len(data): + pos = len(data) - 1 + + count = sum(data[:pos + 1]) + + path = (count,) + self.idle_scroll_path = path + + if self.idle_scroll_id is None: + self.idle_scroll_id = GObject.idle_add(self.idle_scroll) + + return False + + def idle_scroll(self): + + self.idle_scroll_id = None + + if self.idle_scroll_path is None: + return False + + path = self.idle_scroll_path + self.idle_scroll_path = None + + view = self.window.log_view + view.scroll_to_cell(path, use_align=True, row_align=.5) + + return False + + +class TimelineFeature (FeatureBase): + + def __init__(self, app): + + self.logger = logging.getLogger("ui.timeline") + + self.action_group = Gtk.ActionGroup("TimelineActions") + self.action_group.add_toggle_actions([("show-timeline", + None, _("_Timeline"), + "t")]) + + self.state = app.state.sections[TimelineState._name] + + self.attached_windows = {} + + action = self.action_group.get_action("show-timeline") + action.props.active = self.state.shown + action.connect("toggled", self.handle_show_action_toggled) + + def handle_show_action_toggled(self, action): + + self.state.shown = action.props.active + + def handle_attach_window(self, window): + + self.attached_windows[window] = AttachedWindow(self, window) + + def handle_detach_window(self, window): + + attached_window = self.attached_windows.pop(window) + attached_window.detach(self) + + def handle_attach_log_file(self, window, log_file): + + pass + + def handle_detach_log_file(self, window, log_file): + + attached_window = self.attached_windows[window] + attached_window.handle_detach_log_file(log_file) + + +class TimelineState (Common.GUI.StateSection): + + _name = "timeline" + + shown = Common.GUI.StateBool("shown", default=True) + + +class Plugin (PluginBase): + + features = (TimelineFeature,) + + def __init__(self, app): + + app.state.add_section_class(TimelineState) + self.state = app.state.sections[TimelineState._name] diff --git a/debug-viewer/GstDebugViewer/Plugins/__init__.py b/debug-viewer/GstDebugViewer/Plugins/__init__.py new file mode 100644 index 0000000000..847a1a8a25 --- /dev/null +++ b/debug-viewer/GstDebugViewer/Plugins/__init__.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Debug Viewer - View and analyze GStreamer debug log files +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer Debug Viewer Plugins package.""" + +__all__ = ["_", "_N", "FeatureBase", "PluginBase"] + +import os.path + + +def _N(s): + return s + + +def load(paths=()): + + for path in paths: + for plugin_module in _load_plugins(path): + yield plugin_module.Plugin + + +def _load_plugins(path): + + import imp + import glob + + files = glob.glob(os.path.join(path, "*.py")) + + for filename in files: + + name = os.path.basename(os.path.splitext(filename)[0]) + if name == "__init__": + continue + fp, pathname, description = imp.find_module(name, [path]) + module = imp.load_module(name, fp, pathname, description) + yield module + + +class FeatureBase (object): + + def __init__(self, app): + + pass + + def handle_attach_window(self, window): + """ + window: GstDebugViewer.GUI.window.Window + """ + + pass + + def handle_attach_log_file(self, window, log_file): + """ + window: GstDebugViewer.GUI.window.Window + log_file: GstDebugViewer.Data.LogFile + """ + + pass + + def handle_detach_log_file(self, window, log_file): + """ + window: GstDebugViewer.GUI.window.Window + log_file: GstDebugViewer.Data.LogFile + """ + + pass + + def handle_detach_window(self, window): + """ + window: GstDebugViewer.GUI.window.Window + """ + + pass + + +class PluginBase (object): + """ + All plugins must implement a class called Plugin inheriting from PluginBase. + They should place a tuple of features they export into 'features'. Each + feature should be a subclass of FeatureBase. + """ + + features = () + + def __init__(self, app): + + pass diff --git a/debug-viewer/GstDebugViewer/__init__.py b/debug-viewer/GstDebugViewer/__init__.py new file mode 100644 index 0000000000..f0515a918c --- /dev/null +++ b/debug-viewer/GstDebugViewer/__init__.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Debug Viewer - View and analyze GStreamer debug log files +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer Debug Viewer package.""" + +version = "@VERSION@" + +if version.startswith('@'): + version = 'master' + +__version__ = version + +from GstDebugViewer.Main import Paths, GETTEXT_DOMAIN, main as run # noqa diff --git a/debug-viewer/GstDebugViewer/tests/__init__.py b/debug-viewer/GstDebugViewer/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/debug-viewer/GstDebugViewer/tests/create-test-log.py b/debug-viewer/GstDebugViewer/tests/create-test-log.py new file mode 100755 index 0000000000..94c3b6d424 --- /dev/null +++ b/debug-viewer/GstDebugViewer/tests/create-test-log.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python + + +def line_string(ts, pid, thread, level, category, filename, line, function, + object_, message): + + # Replicates gstreamer/gst/gstinfo.c:gst_debug_log_default. + + # FIXME: Regarding object_, this doesn't fully replicate the formatting! + return "%s %5d 0x%x %s %20s %s:%d:%s:<%s> %s" % (Data.time_args(ts), pid, thread, + level.name.ljust( + 5), category, + filename, line, function, + object_, message,) + + +def main(): + + import sys + import os.path + sys.path.append(os.path.dirname(os.path.dirname(sys.argv[0]))) + + global Data + from GstDebugViewer import Data + + count = 100000 + + ts = 0 + pid = 12345 + thread = int("89abcdef", 16) + level = Data.debug_level_log + category = "GST_DUMMY" + filename = "gstdummyfilename.c" + file_line = 1 + function = "gst_dummy_function" + object_ = "dummyobj0" + message = "dummy message with no content" + + levels = (Data.debug_level_log, + Data.debug_level_debug, + Data.debug_level_info,) + + shift = 0 + for i in range(count): + + ts = i * 10000 + shift += i % (count // 100) + level = levels[(i + shift) % 3] + print(line_string(ts, pid, thread, level, category, filename, file_line, + function, object_, message)) + + +if __name__ == "__main__": + main() diff --git a/debug-viewer/GstDebugViewer/tests/performance.py b/debug-viewer/GstDebugViewer/tests/performance.py new file mode 100755 index 0000000000..8f9e225eb2 --- /dev/null +++ b/debug-viewer/GstDebugViewer/tests/performance.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Debug Viewer - View and analyze GStreamer debug log files +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer Debug Viewer performance test program.""" + +import sys +import os +import os.path +from glob import glob +import time + +import gi + +from gi.repository import GObject + +from .. import Common, Data, GUI + + +class TestParsingPerformance (object): + + def __init__(self, filename): + + self.main_loop = GObject.MainLoop() + self.log_file = Data.LogFile(filename, Common.Data.DefaultDispatcher()) + self.log_file.consumers.append(self) + + def start(self): + + self.log_file.start_loading() + + def handle_load_started(self): + + self.start_time = time.time() + + def handle_load_finished(self): + + diff = time.time() - self.start_time + print("line cache built in %0.1f ms" % (diff * 1000.,)) + + start_time = time.time() + model = GUI.LazyLogModel(self.log_file) + for row in model: + pass + diff = time.time() - start_time + print("model iterated in %0.1f ms" % (diff * 1000.,)) + print("overall time spent: %0.1f s" % (time.time() - self.start_time,)) + + import resource + rusage = resource.getrusage(resource.RUSAGE_SELF) + print("time spent in user mode: %.2f s" % (rusage.ru_utime,)) + print("time spent in system mode: %.2f s" % (rusage.ru_stime,)) + + +def main(): + + if len(sys.argv) > 1: + test = TestParsingPerformance(sys.argv[1]) + test.start() + + +if __name__ == "__main__": + main() diff --git a/debug-viewer/GstDebugViewer/tests/test_models.py b/debug-viewer/GstDebugViewer/tests/test_models.py new file mode 100755 index 0000000000..77b2f34589 --- /dev/null +++ b/debug-viewer/GstDebugViewer/tests/test_models.py @@ -0,0 +1,281 @@ +#!/usr/bin/env python +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Debug Viewer - View and analyze GStreamer debug log files +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer Debug Viewer test suite for the custom tree models.""" + +import sys +import os +import os.path +from glob import glob + +from unittest import TestCase, main as test_main + +from .. import Common, Data +from .. GUI.filters import CategoryFilter, Filter +from .. GUI.models import (FilteredLogModel, + LogModelBase, + SubRange,) + + +class TestSubRange (TestCase): + + def test_len(self): + + values = list(range(20)) + + sr = SubRange(values, 0, 20) + self.assertEqual(len(sr), 20) + + sr = SubRange(values, 10, 20) + self.assertEqual(len(sr), 10) + + sr = SubRange(values, 0, 10) + self.assertEqual(len(sr), 10) + + sr = SubRange(values, 5, 15) + self.assertEqual(len(sr), 10) + + def test_iter(self): + + values = list(range(20)) + + sr = SubRange(values, 0, 20) + self.assertEqual(list(sr), values) + + sr = SubRange(values, 10, 20) + self.assertEqual(list(sr), list(range(10, 20))) + + sr = SubRange(values, 0, 10) + self.assertEqual(list(sr), list(range(0, 10))) + + sr = SubRange(values, 5, 15) + self.assertEqual(list(sr), list(range(5, 15))) + + +class Model (LogModelBase): + + def __init__(self): + + LogModelBase.__init__(self) + + for i in range(20): + self.line_offsets.append(i * 100) + self.line_levels.append(Data.debug_level_debug) + + def ensure_cached(self, line_offset): + + pid = line_offset // 100 + if pid % 2 == 0: + category = b"EVEN" + else: + category = b"ODD" + + line_fmt = (b"0:00:00.000000000 %5i 0x0000000 DEBUG " + b"%20s dummy.c:1:dummy: dummy") + line_str = line_fmt % (pid, category,) + log_line = Data.LogLine.parse_full(line_str) + self.line_cache[line_offset] = log_line + + def access_offset(self, line_offset): + + return "" + + +class IdentityFilter (Filter): + + def __init__(self): + + def filter_func(row): + return True + self.filter_func = filter_func + + +class RandomFilter (Filter): + + def __init__(self, seed): + + import random + rand = random.Random() + rand.seed(seed) + + def filter_func(row): + return rand.choice((True, False,)) + self.filter_func = filter_func + + +class TestDynamicFilter (TestCase): + + def test_unset_filter_rerange(self): + + full_model = Model() + filtered_model = FilteredLogModel(full_model) + row_list = self.__row_list + + self.assertEqual(row_list(full_model), list(range(20))) + self.assertEqual(row_list(filtered_model), list(range(20))) + + filtered_model.set_range(5, 16) + + self.assertEqual(row_list(filtered_model), list(range(5, 16))) + + def test_identity_filter_rerange(self): + + full_model = Model() + filtered_model = FilteredLogModel(full_model) + row_list = self.__row_list + + self.assertEqual(row_list(full_model), list(range(20))) + self.assertEqual(row_list(filtered_model), list(range(20))) + + filtered_model.add_filter(IdentityFilter(), + Common.Data.DefaultDispatcher()) + filtered_model.set_range(5, 16) + + self.assertEqual(row_list(filtered_model), list(range(5, 16))) + + def test_filtered_range_refilter_skip(self): + + full_model = Model() + filtered_model = FilteredLogModel(full_model) + + row_list = self.__row_list + + filtered_model.add_filter(CategoryFilter("EVEN"), + Common.Data.DefaultDispatcher()) + self.__dump_model(filtered_model, "filtered") + + self.assertEqual(row_list(filtered_model), list(range(1, 20, 2))) + self.assertEqual([filtered_model.line_index_from_super(i) + for i in range(1, 20, 2)], + list(range(10))) + self.assertEqual([filtered_model.line_index_to_super(i) + for i in range(10)], + list(range(1, 20, 2))) + + filtered_model.set_range(1, 20) + self.__dump_model(filtered_model, "ranged (1, 20)") + self.__dump_model(filtered_model, "filtered range") + + self.assertEqual([filtered_model.line_index_from_super(i) + for i in range(0, 19, 2)], + list(range(10))) + self.assertEqual([filtered_model.line_index_to_super(i) + for i in range(10)], + list(range(1, 20, 2))) + + filtered_model.set_range(2, 20) + self.__dump_model(filtered_model, "ranged (2, 20)") + + self.assertEqual(row_list(filtered_model), list(range(3, 20, 2))) + + def test_filtered_range_refilter(self): + + full_model = Model() + filtered_model = FilteredLogModel(full_model) + + row_list = self.__row_list + rows = row_list(full_model) + rows_filtered = row_list(filtered_model) + + self.__dump_model(full_model, "full model") + + self.assertEqual(rows, rows_filtered) + + self.assertEqual([filtered_model.line_index_from_super(i) + for i in range(20)], + list(range(20))) + self.assertEqual([filtered_model.line_index_to_super(i) + for i in range(20)], + list(range(20))) + + filtered_model.set_range(5, 16) + self.__dump_model(filtered_model, "ranged model (5, 16)") + + rows_ranged = row_list(filtered_model) + self.assertEqual(rows_ranged, list(range(5, 16))) + + self.__dump_model(filtered_model, "filtered model (nofilter, 5, 15)") + + filtered_model.add_filter(CategoryFilter("EVEN"), + Common.Data.DefaultDispatcher()) + rows_filtered = row_list(filtered_model) + self.assertEqual(rows_filtered, list(range(5, 16, 2))) + + self.__dump_model(filtered_model, "filtered model") + + def test_random_filtered_range_refilter(self): + + full_model = Model() + filtered_model = FilteredLogModel(full_model) + row_list = self.__row_list + + self.assertEqual(row_list(full_model), list(range(20))) + self.assertEqual(row_list(filtered_model), list(range(20))) + + filtered_model.add_filter(RandomFilter(538295943), + Common.Data.DefaultDispatcher()) + random_rows = row_list(filtered_model) + + self.__dump_model(filtered_model) + + filtered_model = FilteredLogModel(full_model) + filtered_model.add_filter(RandomFilter(538295943), + Common.Data.DefaultDispatcher()) + self.__dump_model(filtered_model, "filtered model") + self.assertEqual(row_list(filtered_model), random_rows) + + filtered_model.set_range(1, 10) + self.__dump_model(filtered_model) + self.assertEqual(row_list(filtered_model), [ + x for x in range(0, 10) if x in random_rows]) + + def __row_list(self, model): + + return [row[Model.COL_PID] for row in model] + + def __dump_model(self, model, comment=None): + + # TODO: Provide a command line option to turn this on and off. + + return + + if not hasattr(model, "super_model"): + # Top model. + print("\t(%s)" % ("|".join([str(i).rjust(2) + for i in self.__row_list(model)]),), end=' ') + else: + top_model = model.super_model + if hasattr(top_model, "super_model"): + top_model = top_model.super_model + top_indices = self.__row_list(top_model) + positions = self.__row_list(model) + output = [" "] * len(top_indices) + for i, position in enumerate(positions): + output[position] = str(i).rjust(2) + print("\t(%s)" % ("|".join(output),), end=' ') + + if comment is None: + print() + else: + print(comment) + + +if __name__ == "__main__": + test_main() diff --git a/debug-viewer/MANIFEST.in b/debug-viewer/MANIFEST.in new file mode 100644 index 0000000000..acf1c18b2f --- /dev/null +++ b/debug-viewer/MANIFEST.in @@ -0,0 +1,9 @@ +recursive-include GstDebugViewer *.py +recursive-include data *.glade *.ui *.svg *.png +recursive-include po *.po +recursive-include tests *.py +include gst-debug-viewer +include gst-debug-viewer.desktop.in +include org.freedesktop.GstDebugViewer.appdata.xml.in +include AUTHORS COPYING ChangeLog MANIFEST.in NEWS README TODO +include po/POTFILES.in diff --git a/debug-viewer/README b/debug-viewer/README new file mode 100644 index 0000000000..f517ae8cfd --- /dev/null +++ b/debug-viewer/README @@ -0,0 +1,36 @@ +# how to build # + +./setup.py build; sudo ./setup.py install --prefix=/usr +sudo chmod a+r /usr/share/gst-debug-viewer/*.ui + +# porting issues # + +http://stackoverflow.com/questions/11025700/generictreemodel-with-pygobject-introspection-gtk-3 + +# tips # + +OLD: prev_action.connect_proxy(prev_button) +NEW: prev_button.set_related_action (prev_action) + +OLD: box.pack_start (widget) +NEW: box.pack_start (widget, True, True, 0) + +OLD: column.pack_start (cell) +NEW: column.pack_start (cell, True) + +OLD: view_column.get_cell_renderers () +NEW: column.get_cells () + +# porting ressources # +https://www.xpra.org/trac/ticket/90?cversion=0&cnum_hist=3 +https://mail.gnome.org/archives/commits-list/2013-October/msg05205.html + +# profiling # +python -m profile -o output.pstats path/to/your/script arg1 arg2 +gprof2dot.py -f pstats output.pstats | dot -Tpng -o output.png +~/projects/tools/gprof2dot/gprof2dot.py -f pstats output.pstats | dot -Tpng -o output.png +eog output.png + +python -m cProfile -o output.pstats2 ./gst-debug-viewer debug.noansi.log +~/projects/tools/gprof2dot/gprof2dot.py -f pstats output.pstats2 | dot -Tpng -o output2.png +eog output2.png diff --git a/debug-viewer/data/about-dialog.ui b/debug-viewer/data/about-dialog.ui new file mode 100644 index 0000000000..0a45bceb79 --- /dev/null +++ b/debug-viewer/data/about-dialog.ui @@ -0,0 +1,701 @@ + + + + + + True + 5 + dialog + Copyright © 2007-2009 René Stadler + View and analyze GStreamer debug files + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<http://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<http://www.gnu.org/philosophy/why-not-lgpl.html>. + René Stadler <mail@renestadler.de> + translator-credits + gst-debug-viewer.png + + + + + + False + end + 0 + + + + + + diff --git a/debug-viewer/data/gst-debug-viewer.png b/debug-viewer/data/gst-debug-viewer.png new file mode 100644 index 0000000000..81099a6853 Binary files /dev/null and b/debug-viewer/data/gst-debug-viewer.png differ diff --git a/debug-viewer/data/gst-debug-viewer.svg b/debug-viewer/data/gst-debug-viewer.svg new file mode 100644 index 0000000000..f9f5d91f95 --- /dev/null +++ b/debug-viewer/data/gst-debug-viewer.svg @@ -0,0 +1,399 @@ + + +image/svg+xml + + + + + + + + + + + + + \ No newline at end of file diff --git a/debug-viewer/data/main-window.ui b/debug-viewer/data/main-window.ui new file mode 100644 index 0000000000..1867c70ddb --- /dev/null +++ b/debug-viewer/data/main-window.ui @@ -0,0 +1,88 @@ + + + + + + GStreamer Debug Viewer + 640 + 480 + + + + True + + + True + + + True + True + + + True + + + True + True + automatic + automatic + in + + + log_view + True + True + True + True + False + True + + + + + end + 0 + + + + + True + True + + + + + True + True + never + automatic + in + + + True + True + False + True + + + + + False + True + + + + + 0 + + + + + end + 0 + + + + + + diff --git a/debug-viewer/data/menus.ui b/debug-viewer/data/menus.ui new file mode 100644 index 0000000000..20d6424ff4 --- /dev/null +++ b/debug-viewer/data/menus.ui @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/debug-viewer/data/meson.build b/debug-viewer/data/meson.build new file mode 100644 index 0000000000..724983bfcd --- /dev/null +++ b/debug-viewer/data/meson.build @@ -0,0 +1,6 @@ +install_data('about-dialog.ui', 'main-window.ui', 'menus.ui', 'gst-debug-viewer.png', + install_dir: join_paths(get_option('datadir'), 'gst-debug-viewer')) +install_data('gst-debug-viewer.png', + install_dir: join_paths(get_option('datadir'), 'icons/hicolor/48x48/apps')) +install_data('gst-debug-viewer.svg', + install_dir: join_paths(get_option('datadir'), 'icons/hicolor/scalable/apps')) \ No newline at end of file diff --git a/debug-viewer/gst-debug-viewer b/debug-viewer/gst-debug-viewer new file mode 100755 index 0000000000..7bf451e2b8 --- /dev/null +++ b/debug-viewer/gst-debug-viewer @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Debug Viewer - View and analyze GStreamer debug log files +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer Debug Viewer program invocation.""" + + +def main(): + + import sys + import os.path + + def substituted(s): + if s.startswith("@") and s.endswith("@"): + return None + else: + return s + + # These "$"-enclosed strings are substituted at install time by a custom + # distutils extension (see setup.py). If you don't see any dollar signs at + # all, you are looking at an installed version of this file. + data_dir = substituted("@DATADIR@") + lib_dir = substituted("@LIBDIR@") + + if data_dir: + installed = True + else: + # Substitution has not been run, we are running uninstalled: + lib_dir = os.path.dirname(os.path.realpath(sys.argv[0])) + installed = False + + if lib_dir: + if not os.path.normpath(lib_dir) in [os.path.normpath(p) + for p in sys.path]: + sys.path.insert(0, lib_dir) + + try: + import GstDebugViewer + except ImportError as exc: + print(str(exc), file=sys.stderr) + sys.exit(1) + else: + if installed: + GstDebugViewer.Paths.setup_installed(data_dir) + else: + # Assume that we reside inside the source dist. + source_dir = os.path.dirname(os.path.realpath(sys.argv[0])) + GstDebugViewer.Paths.setup_uninstalled(source_dir) + + GstDebugViewer.run() + + +if __name__ == "__main__": + main() diff --git a/debug-viewer/meson.build b/debug-viewer/meson.build new file mode 100644 index 0000000000..263b7958a2 --- /dev/null +++ b/debug-viewer/meson.build @@ -0,0 +1,85 @@ +python3.install_sources ( + 'GstDebugViewer/Main.py', + 'GstDebugViewer/Data.py', + subdir: 'GstDebugViewer') + +python3.install_sources ( + 'GstDebugViewer/GUI/columns.py', + 'GstDebugViewer/GUI/__init__.py', + 'GstDebugViewer/GUI/models.py', + 'GstDebugViewer/GUI/filters.py', + 'GstDebugViewer/GUI/colors.py', + 'GstDebugViewer/GUI/window.py', + 'GstDebugViewer/GUI/app.py', + subdir: 'GstDebugViewer/GUI') + +python3.install_sources ( + 'GstDebugViewer/Plugins/__init__.py', + 'GstDebugViewer/Plugins/FindBar.py', + 'GstDebugViewer/Plugins/Timeline.py', + subdir: 'GstDebugViewer/Plugins') + +python3.install_sources ( + 'GstDebugViewer/Common/Main.py', + 'GstDebugViewer/Common/utils.py', + 'GstDebugViewer/Common/__init__.py', + 'GstDebugViewer/Common/generictreemodel.py', + 'GstDebugViewer/Common/Data.py', + 'GstDebugViewer/Common/GUI.py', + subdir: 'GstDebugViewer/Common') + +if find_program('msgfmt', required : get_option('nls')).found() + # Desktop launcher and description file. + desktop_file = i18n.merge_file( + input: 'org.freedesktop.GstDebugViewer.desktop.in', + output: 'org.freedesktop.GstDebugViewer.desktop', + type: 'desktop', + po_dir: 'po', + install: true, + install_dir: join_paths(get_option('datadir'), 'applications'), + ) + + # Appdata file. + appdata_file = i18n.merge_file( + input: 'org.freedesktop.GstDebugViewer.appdata.xml.in', + output: 'org.freedesktop.GstDebugViewer.appdata.xml', + po_dir: 'po', + install: true, + install_dir: join_paths(get_option('datadir'), 'metainfo'), + ) +else + install_data('org.freedesktop.GstDebugViewer.desktop.in', + rename: 'org.freedesktop.GstDebugViewer.desktop', + install_dir: join_paths(get_option('datadir'), 'applications')) + install_data('org.freedesktop.GstDebugViewer.appdata.xml.in', + rename: 'org.freedesktop.GstDebugViewer.appdata.xml', + install_dir: join_paths(get_option('datadir'), 'metainfo')) +endif + +cdata = configuration_data() +cdata.set('LIBDIR', join_paths(get_option('prefix'), get_option('libdir'))) +cdata.set('DATADIR', join_paths(get_option('prefix'), get_option('datadir'))) +cdata.set('VERSION', meson.project_version()) + +configure_file(input: 'gst-debug-viewer', + output: 'gst-debug-viewer', + configuration: cdata, + install_dir: get_option('bindir')) + +init_file = configure_file( + input: 'GstDebugViewer/__init__.py', + output: '__init__.py', + configuration: cdata) +python3.install_sources (init_file, subdir: 'GstDebugViewer') + +pkgdatadir = join_paths(get_option('datadir'), meson.project_name()) +icondir = join_paths(get_option('datadir'), 'icons/hicolor') + +subdir('data') + + +if run_command(python3, + '-c', 'import gi; gi.require_version("Gtk", "3.0")').returncode() == 0 + test('gst-debug-viewer', python3, args: ['-m', 'unittest'], + workdir: meson.current_source_dir()) +endif diff --git a/debug-viewer/org.freedesktop.GstDebugViewer.appdata.xml.in b/debug-viewer/org.freedesktop.GstDebugViewer.appdata.xml.in new file mode 100644 index 0000000000..1a95d6d6af --- /dev/null +++ b/debug-viewer/org.freedesktop.GstDebugViewer.appdata.xml.in @@ -0,0 +1,29 @@ + + + org.freedesktop.GstDebugViewer + org.freedesktop.GstDebugViewer.desktop + CC-BY-3.0 + GPL-3.0+ + GStreamer Debug Viewer + Examine GStreamer debug log information + +

View and read GStreamer debug logs in an efficient way

+
+ https://gstreamer.freedesktop.org/ + https://gitlab.freedesktop.org/gstreamer/gst-devtools/issues/ + tsaunier@gnome.org + GStreamer + GStreamer + The GStreamer Team + + + The main window + https://gitlab.freedesktop.org/gstreamer/gst-devtools/-/raw/master/debug-viewer/screenshots/gst-debug-viewer.png?inline=false + + + + + + + +
diff --git a/debug-viewer/org.freedesktop.GstDebugViewer.desktop.in b/debug-viewer/org.freedesktop.GstDebugViewer.desktop.in new file mode 100644 index 0000000000..47ee33f36c --- /dev/null +++ b/debug-viewer/org.freedesktop.GstDebugViewer.desktop.in @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=GStreamer Debug Viewer +Comment=Examine GStreamer debug log information +StartupNotify=true +Exec=gst-debug-viewer +Icon=gst-debug-viewer +Type=Application +Categories=GNOME;Development + diff --git a/debug-viewer/pixmaps/gst-debug-viewer.png b/debug-viewer/pixmaps/gst-debug-viewer.png new file mode 100644 index 0000000000..81099a6853 Binary files /dev/null and b/debug-viewer/pixmaps/gst-debug-viewer.png differ diff --git a/debug-viewer/po/LINGUAS b/debug-viewer/po/LINGUAS new file mode 100644 index 0000000000..e69de29bb2 diff --git a/debug-viewer/po/POTFILES.in b/debug-viewer/po/POTFILES.in new file mode 100644 index 0000000000..e69de29bb2 diff --git a/debug-viewer/screenshots/gst-debug-viewer.png b/debug-viewer/screenshots/gst-debug-viewer.png new file mode 100644 index 0000000000..e4a4cbd59c Binary files /dev/null and b/debug-viewer/screenshots/gst-debug-viewer.png differ diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000000..c406aa9ae9 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,6 @@ +# GstValidate API reference + +This is GstValidate API reference but note that the GstValidate is not +totally stable and might very well change even between minor versions. + +The override API should be mostly stable still. diff --git a/docs/fakesrc.simple.validatetest b/docs/fakesrc.simple.validatetest new file mode 100644 index 0000000000..8b9416c11c --- /dev/null +++ b/docs/fakesrc.simple.validatetest @@ -0,0 +1,8 @@ +meta, + args = { + "fakesrc num-buffers=1 ! fakesink name=sink", + }, + configs = { + "$(validateflow), pad=sink:sink, buffers-checksum=true", + } +# The validate tool will simply play the pipeline until EOS is reached. \ No newline at end of file diff --git a/docs/fakesrc.simple.validatetest.ini b/docs/fakesrc.simple.validatetest.ini new file mode 120000 index 0000000000..1b6fe82b5a --- /dev/null +++ b/docs/fakesrc.simple.validatetest.ini @@ -0,0 +1 @@ +fakesrc.simple.validatetest \ No newline at end of file diff --git a/docs/fakesrc.simple/flow-expectations/log-sink-sink-expected b/docs/fakesrc.simple/flow-expectations/log-sink-sink-expected new file mode 100644 index 0000000000..6792b8cdc6 --- /dev/null +++ b/docs/fakesrc.simple/flow-expectations/log-sink-sink-expected @@ -0,0 +1,4 @@ +event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE, group-id=(uint)1; +event segment: format=BYTES, start=0, offset=0, stop=18446744073709551615, time=0, base=0, position=0 +buffer: checksum=da39a3ee5e6b4b0d3255bfef95601890afd80709, dts=0:00:00.000000000, flags=discont +event eos: (no structure) diff --git a/docs/ges-validate-action-types.md b/docs/ges-validate-action-types.md new file mode 100644 index 0000000000..09973b7bbd --- /dev/null +++ b/docs/ges-validate-action-types.md @@ -0,0 +1,1040 @@ +# GES action types + +## edit-container + +``` validate-scenario +edit-container, + [playback-time=(double,string)], + container-name=(string), + position=(double or string), + [edit-mode=(string)], + [edge=(string)], + [new-layer-priority=(int)]; +``` + +Allows to edit a container (like a GESClip), for more details, have a look at: +ges_container_edit documentation, Note that the timeline will +be commited, and flushed so that the edition is taken into account + * Implementer namespace: ges + +### Parameters + + Parameters: + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +* `container-name`:(mandatory): The name of the GESContainer to edit + + Possible types: `string` + +* `position`:(mandatory): The new position of the GESContainer + + Possible variables: + + * position: The current position in the stream + + * duration: The duration of the stream + + Possible types: `double or string` + +* `edit-mode`:(optional): The GESEditMode to use to edit @container-name + + Possible types: `string` + + Default: normal + +* `edge`:(optional): The GESEdge to use to edit @container-name +should be in [ edge_start, edge_end, edge_none ] + + Possible types: `string` + + Default: edge_none + +* `new-layer-priority`:(optional): The priority of the layer @container should land in. +If the layer you're trying to move the container to doesn't exist, it will +be created automatically. -1 means no move. + + Possible types: `int` + + Default: -1 + +## add-asset + + +``` validate-scenario +add-asset, + [playback-time=(double,string)], + id, + type; +``` + +Allows to add an asset to the current project + * Implementer namespace: ges + +### Parameters + + Parameters: + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +* `id`:(mandatory): Adds an asset to a project. + +* `type`:(mandatory): The type of asset to add + +## remove-asset + + +``` validate-scenario +remove-asset, + [playback-time=(double,string)], + id, + type; +``` + +Allows to remove an asset from the current project + * Implementer namespace: ges + +### Parameters + + Parameters: + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +* `id`:(mandatory): The ID of the clip to remove + +* `type`:(mandatory): The type of asset to remove + +## add-layer + + +``` validate-scenario +add-layer, + [playback-time=(double,string)], + [priority]; +``` + +Allows to add a layer to the current timeline + * Implementer namespace: ges + +### Parameters + + Parameters: + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +* `priority`:(optional): The priority of the new layer to add,if not specified, the new layer will be appended to the timeline + + Default: (null) + +## remove-layer + + +``` validate-scenario +remove-layer, + [playback-time=(double,string)], + priority, + [auto-transition=(boolean)]; +``` + +Allows to remove a layer from the current timeline + * Implementer namespace: ges + +### Parameters + + Parameters: + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +* `priority`:(mandatory): The priority of the layer to remove + +* `auto-transition`:(optional): Wheter auto-transition is activated on the new layer. + + Possible types: `boolean` + + Default: False + +## add-clip + + +``` validate-scenario +add-clip, + [playback-time=(double,string)], + name=(string), + layer-priority=(int), + asset-id=(string), + type=(string), + [start=(double or string)], + [inpoint=(double or string)], + [duration=(double or string)]; +``` + +Allows to add a clip to a given layer + * Implementer namespace: ges + +### Parameters + + Parameters: + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +* `name`:(mandatory): The name of the clip to add + + Possible types: `string` + +* `layer-priority`:(mandatory): The priority of the clip to add + + Possible types: `int` + +* `asset-id`:(mandatory): The id of the asset from which to extract the clip + + Possible types: `string` + +* `type`:(mandatory): The type of the clip to create + + Possible types: `string` + +* `start`:(optional): The start value to set on the new GESClip. + + Possible types: `double or string` + + Default: (null) + +* `inpoint`:(optional): The inpoint value to set on the new GESClip + + Possible types: `double or string` + + Default: (null) + +* `duration`:(optional): The duration value to set on the new GESClip + + Possible types: `double or string` + + Default: (null) + +## remove-clip + + +``` validate-scenario +remove-clip, + [playback-time=(double,string)], + name=(string); +``` + +Allows to remove a clip from a given layer + * Implementer namespace: ges + +### Parameters + + Parameters: + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +* `name`:(mandatory): The name of the clip to remove + + Possible types: `string` + +## serialize-project + + +``` validate-scenario +serialize-project, + [playback-time=(double,string)], + uri=(string); +``` + +serializes a project + * Implementer namespace: ges + +### Parameters + + Parameters: + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +* `uri`:(mandatory): The uri where to store the serialized project + + Possible types: `string` + +## set-child-property + + +``` validate-scenario +set-child-property, + [playback-time=(double,string)], + element-name=(string), + property=(string), + value=(gvalue); +``` + +Allows to change child property of an object + * Implementer namespace: ges + +### Parameters + + Parameters: + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +* `element-name`:(mandatory): The name of the element on which to modify the property + + Possible types: `string` + +* `property`:(mandatory): The name of the property to modify + + Possible types: `string` + +* `value`:(mandatory): The value of the property + + Possible types: `gvalue` + +## split-clip + + +``` validate-scenario +split-clip, + [playback-time=(double,string)], + clip-name=(string), + position=(double or string); +``` + +Split a clip at a specified position. + * Implementer namespace: ges + +### Parameters + + Parameters: + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +* `clip-name`:(mandatory): The name of the clip to split + + Possible types: `string` + +* `position`:(mandatory): The position at which to split the clip + + Possible types: `double or string` + +## set-track-restriction-caps + + +``` validate-scenario +set-track-restriction-caps, + [playback-time=(double,string)], + track-type=(string), + caps=(string); +``` + +Sets restriction caps on tracks of a specific type. + * Implementer namespace: ges + +### Parameters + + Parameters: + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +* `track-type`:(mandatory): The type of track to set restriction caps on + + Possible types: `string` + +* `caps`:(mandatory): The caps to set on the track + + Possible types: `string` + +## element-set-asset + + +``` validate-scenario +element-set-asset, + [playback-time=(double,string)], + element-name=(string), + asset-id=(string); +``` + +Sets restriction caps on tracks of a specific type. + * Implementer namespace: ges + +### Parameters + + Parameters: + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +* `element-name`:(mandatory): The name of the TimelineElement to set an asset on + + Possible types: `string` + +* `asset-id`:(mandatory): The id of the asset from which to extract the clip + + Possible types: `string` + +## container-add-child + + +``` validate-scenario +container-add-child, + [playback-time=(double,string)], + container-name=(string), + [child-name=(string)], + asset-id=(string), + [child-type=(string)]; +``` + +Add a child to @container-name. If asset-id and child-type are specified, the child will be created and added. Otherwize @child-name has to be specified and will be added to the container. + * Implementer namespace: ges + +### Parameters + + Parameters: + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +* `container-name`:(mandatory): The name of the GESContainer to add a child to + + Possible types: `string` + +* `child-name`:(optional): The name of the child to add to @container-name + + Possible types: `string` + + Default: NULL + +* `asset-id`:(mandatory): The id of the asset from which to extract the child + + Possible types: `string` + +* `child-type`:(optional): The type of the child to create + + Possible types: `string` + + Default: NULL + +## container-remove-child + + +``` validate-scenario +container-remove-child, + [playback-time=(double,string)], + container-name=(string), + child-name=(string); +``` + +Remove a child from @container-name. + * Implementer namespace: ges + +### Parameters + + Parameters: + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +* `container-name`:(mandatory): The name of the GESContainer to remove a child from + + Possible types: `string` + +* `child-name`:(mandatory): The name of the child to reomve from @container-name + + Possible types: `string` + +## ungroup-container + + +``` validate-scenario +ungroup-container, + [playback-time=(double,string)], + container-name=(string), + [recursive=(boolean)]; +``` + +Ungroup children of @container-name. + * Implementer namespace: ges + +### Parameters + + Parameters: + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +* `container-name`:(mandatory): The name of the GESContainer to ungroup children from + + Possible types: `string` + +* `recursive`:(optional): Wether to recurse ungrouping or not. + + Possible types: `boolean` + + Default: (null) + +## set-control-source + + +``` validate-scenario +set-control-source, + [playback-time=(double,string)], + element-name=(string), + property-name=(string), + [binding-type=(string)], + [source-type=(string)], + [interpolation-mode=(string)]; +``` + +Adds a GstControlSource on @element-name::@property-name allowing you to then add keyframes on that property. + * Implementer namespace: ges + +### Parameters + + Parameters: + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +* `element-name`:(mandatory): The name of the GESTrackElement to set the control source on + + Possible types: `string` + +* `property-name`:(mandatory): The name of the property for which to set a control source + + Possible types: `string` + +* `binding-type`:(optional): The name of the type of binding to use + + Possible types: `string` + + Default: direct + +* `source-type`:(optional): The name of the type of ControlSource to use + + Possible types: `string` + + Default: interpolation + +* `interpolation-mode`:(optional): The name of the GstInterpolationMode to on the source + + Possible types: `string` + + Default: linear + +## add-keyframe + + +``` validate-scenario +add-keyframe, + [playback-time=(double,string)], + element-name=(string), + property-name=(string), + timestamp=(string or float), + value=(float); +``` + +Remove a child from @container-name. + * Implementer namespace: ges + +### Parameters + + Parameters: + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +* `element-name`:(mandatory): The name of the GESTrackElement to add a keyframe on + + Possible types: `string` + +* `property-name`:(mandatory): The name of the property for which to add a keyframe on + + Possible types: `string` + +* `timestamp`:(mandatory): The timestamp of the keyframe + + Possible types: `string or float` + +* `value`:(mandatory): The value of the keyframe + + Possible types: `float` + +## copy-element + + +``` validate-scenario +copy-element, + [playback-time=(double,string)], + element-name=(string), + [recurse=(boolean)], + position=(string or float), + [paste-name=(string)]; +``` + +Remove a child from @container-name. + * Implementer namespace: ges + +### Parameters + + Parameters: + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +* `element-name`:(mandatory): The name of the GESTtimelineElement to copy + + Possible types: `string` + +* `recurse`:(optional): Copy recursively or not + + Possible types: `boolean` + + Default: true + +* `position`:(mandatory): The time where to paste the element + + Possible types: `string or float` + +* `paste-name`:(optional): The name of the copied element + + Possible types: `string` + + Default: (null) + +## remove-keyframe + + +``` validate-scenario +remove-keyframe, + [playback-time=(double,string)], + element-name=(string), + property-name=(string), + timestamp=(string or float); +``` + +Remove a child from @container-name. + * Implementer namespace: ges + +### Parameters + + Parameters: + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +* `element-name`:(mandatory): The name of the GESTrackElement to add a keyframe on + + Possible types: `string` + +* `property-name`:(mandatory): The name of the property for which to add a keyframe on + + Possible types: `string` + +* `timestamp`:(mandatory): The timestamp of the keyframe + + Possible types: `string or float` + +## load-project + + +``` validate-scenario +load-project, + [playback-time=(double,string)], + serialized-content; +``` + +Loads a project either from its content passed in the serialized-content field. +Note that it will completely clean the previous timeline + * Implementer namespace: ges + +### Parameters + + Parameters: + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +* `serialized-content`:(mandatory): The full content of the XML describing project in XGES formet. + +## commit + + +``` validate-scenario +commit, + [playback-time=(double,string)]; +``` + +Commit the timeline. + * Implementer namespace: ges + +### Parameters + + Parameters: + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) diff --git a/docs/gi-index.md b/docs/gi-index.md new file mode 100644 index 0000000000..dc270fbddc --- /dev/null +++ b/docs/gi-index.md @@ -0,0 +1 @@ +# GstValidate API reference diff --git a/docs/gst-validate-action-types.md b/docs/gst-validate-action-types.md new file mode 100644 index 0000000000..65c3105f07 --- /dev/null +++ b/docs/gst-validate-action-types.md @@ -0,0 +1,1531 @@ +# GstValidate action types + +## meta + + +``` validate-scenario +meta, + [duration=(double, int)], + [handles-states=(boolean)], + [ignore-eos=(boolean)], + [is-config=(boolean)], + [max-dropped=(int)], + [max-latency=(double, int)], + [min-audio-track=(int)], + [min-media-duration=(double)], + [min-video-track=(int)], + [need-clock-sync=(boolean)], + [pipeline-name=(string)], + [reverse-playback=(boolean)], + [seek=(boolean)], + [summary=(string)]; +``` + +Scenario metadata. +NOTE: it used to be called "description" + * Implementer namespace: core + * Is config action (meaning it will be executing right at the beginning of the execution of the pipeline) + +### Parameters + +* `duration`:(optional): Lets the user know the time the scenario needs to be fully executed + + Possible types: `double, int` + + Default: infinite (GST_CLOCK_TIME_NONE) + +* `handles-states`:(optional): Whether the scenario handles pipeline state changes from the beginning +in that case the application should not set the state of the pipeline to anything +and the scenario action will be executed from the beginning + + Possible types: `boolean` + + Default: false + +* `ignore-eos`:(optional): Ignore EOS and keep executing the scenario when it happens. + By default a 'stop' action is generated one EOS + + Possible types: `boolean` + + Default: false + +* `is-config`:(optional): Whether the scenario is a config only scenario + + Possible types: `boolean` + + Default: false + +* `max-dropped`:(optional): The maximum number of buffers which can be dropped by the QoS system allowed for this pipeline. +It can be overridden using core configuration, like for example by defining the env variable GST_VALIDATE_CONFIG=core,max-dropped=100 + + Possible types: `int` + + Default: infinite (-1) + +* `max-latency`:(optional): The maximum latency in nanoseconds allowed for this pipeline. +It can be overridden using core configuration, like for example by defining the env variable GST_VALIDATE_CONFIG=core,max-latency=33000000 + + Possible types: `double, int` + + Default: infinite (GST_CLOCK_TIME_NONE) + +* `min-audio-track`:(optional): Lets the user know the minimum number of audio tracks the stream needs to contain +for the scenario to be usable + + Possible types: `int` + + Default: 0 + +* `min-media-duration`:(optional): Lets the user know the minimum duration of the stream for the scenario +to be usable + + Possible types: `double` + + Default: 0.0 + +* `min-video-track`:(optional): Lets the user know the minimum number of video tracks the stream needs to contain +for the scenario to be usable + + Possible types: `int` + + Default: 0 + +* `need-clock-sync`:(optional): Whether the scenario needs the execution to be synchronized with the pipeline's +clock. Letting the user know if it can be used with a 'fakesink sync=false' sink + + Possible types: `boolean` + + Default: true if some action requires a playback-time false otherwise + +* `pipeline-name`:(optional): The name of the GstPipeline on which the scenario should be executed. +It has the same effect as setting the pipeline using pipeline_name->scenario_name. + + Possible types: `string` + + Default: NULL + +* `reverse-playback`:(optional): Whether the scenario plays the stream backward + + Possible types: `boolean` + + Default: false + +* `seek`:(optional): Whether the scenario executes seek actions or not + + Possible types: `boolean` + + Default: false + +* `summary`:(optional): Whether the scenario is a config only scenario (ie. explain what it does) + + Possible types: `string` + + Default: 'Nothing' + +## seek + + +``` validate-scenario +seek, + flags=(string describing the GstSeekFlags to set), + start=(double or string (GstClockTime)), + [rate=(double)], + [start_type=(string)], + [stop=(double or string (GstClockTime))], + [stop_type=(string)], + [playback-time=(double,string)]; +``` + +Seeks into the stream. This is an example of a seek happening when the stream reaches 5 seconds +or 1 eighth of its duration and seeks to 10s or 2 eighths of its duration: + seek, playback-time="min(5.0, (duration/8))", start="min(10, 2*(duration/8))", flags=accurate+flush + * Implementer namespace: core + +### Parameters + +* `flags`:(mandatory): The GstSeekFlags to use + + Possible types: `string describing the GstSeekFlags to set` + +* `start`:(mandatory): The starting value of the seek + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double or string (GstClockTime)` + +* `rate`:(optional): The rate value of the seek + + Possible types: `double` + + Default: 1.0 + +* `start_type`:(optional): The GstSeekType to use for the start of the seek, in: + [none, set, end] + + Possible types: `string` + + Default: set + +* `stop`:(optional): The stop value of the seek + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double or string (GstClockTime)` + + Default: GST_CLOCK_TIME_NONE + +* `stop_type`:(optional): The GstSeekType to use for the stop of the seek, in: + [none, set, end] + + Possible types: `string` + + Default: set + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## pause + + +``` validate-scenario +pause, + [duration=(double or string (GstClockTime))], + [playback-time=(double,string)]; +``` + +Sets pipeline to PAUSED. You can add a 'duration' +parameter so the pipeline goes back to playing after that duration +(in second) + * Implementer namespace: core + +### Parameters + +* `duration`:(optional): The duration during which the stream will be paused + + Possible types: `double or string (GstClockTime)` + + Default: 0.0 + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## play + + +``` validate-scenario +play, + [playback-time=(double,string)]; +``` + +Sets the pipeline state to PLAYING + * Implementer namespace: core + +### Parameters + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## stop + + +``` validate-scenario +stop, + [playback-time=(double,string)]; +``` + +Stops the execution of the scenario. It will post a 'request-state' message on the bus with NULL as a requested state and the application is responsible for stopping itself. If you override that action type, make sure to link up. + * Implementer namespace: core + +### Parameters + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## eos + + +``` validate-scenario +eos, + [playback-time=(double,string)]; +``` + +Sends an EOS event to the pipeline + * Implementer namespace: core + +### Parameters + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## switch-track + + +``` validate-scenario +switch-track, + [index=(string: to switch track relatively +int: To use the actual index to use)], + [type=(string)], + [playback-time=(double,string)]; +``` + +The 'switch-track' command can be used to switch tracks. + * Implementer namespace: core + +### Parameters + +* `index`:(optional): Selects which track of this type to use: it can be either a number, +which will be the Nth track of the given type, or a number with a '+' or +'-' prefix, which means a relative change (eg, '+1' means 'next track', +'-1' means 'previous track') + + Possible types: `string: to switch track relatively +int: To use the actual index to use` + + Default: +1 + +* `type`:(optional): Selects which track type to change (can be 'audio', 'video', or 'text'). + + Possible types: `string` + + Default: audio + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## wait + + +``` validate-scenario +wait, + [duration=(double or string (GstClockTime))], + [message-type=(string)], + [signal-name=(string)], + [target-element-name=(string)], + [playback-time=(double,string)]; +``` + +Waits for signal 'signal-name', message 'message-type', or during 'duration' seconds + * Implementer namespace: core + +### Parameters + +* `duration`:(optional): the duration while no other action will be executed + + Possible types: `double or string (GstClockTime)` + + Default: (null) + +* `message-type`:(optional): The name of the message type to wait for (on @target-element-name if specified) + + Possible types: `string` + + Default: (null) + +* `signal-name`:(optional): The name of the signal to wait for on @target-element-name + + Possible types: `string` + + Default: (null) + +* `target-element-name`:(optional): The name of the GstElement to wait @signal-name on. + + Possible types: `string` + + Default: (null) + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## dot-pipeline + + +``` validate-scenario +dot-pipeline, + [playback-time=(double,string)]; +``` + +Dots the pipeline (the 'name' property will be used in the dot filename). +For more information have a look at the GST_DEBUG_BIN_TO_DOT_FILE documentation. +Note that the GST_DEBUG_DUMP_DOT_DIR env variable needs to be set + * Implementer namespace: core + +### Parameters + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## set-rank + + +``` validate-scenario +set-rank, + name=(string), + rank=(string, int); +``` + +Changes the ranking of a particular plugin feature(s) + * Implementer namespace: core + * Is config action (meaning it will be executing right at the beginning of the execution of the pipeline) + +### Parameters + +* `name`:(mandatory): The name of a GstFeature or GstPlugin + + Possible types: `string` + +* `rank`:(mandatory): The GstRank to set on @name + + Possible types: `string, int` + +## set-feature-rank + + +``` validate-scenario +set-feature-rank, + feature-name=(string), + rank=(string, int); +``` + +Changes the ranking of a particular plugin feature + * Implementer namespace: core + * Is config action (meaning it will be executing right at the beginning of the execution of the pipeline) + +### Parameters + +* `feature-name`:(mandatory): The name of a GstFeature + + Possible types: `string` + +* `rank`:(mandatory): The GstRank to set on @feature-name + + Possible types: `string, int` + +## set-state + + +``` validate-scenario +set-state, + state=(string), + [playback-time=(double,string)]; +``` + +Changes the state of the pipeline to any GstState + * Implementer namespace: core + +### Parameters + +* `state`:(mandatory): A GstState as a string, should be in: + * ['null', 'ready', 'paused', 'playing'] + + Possible types: `string` + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## set-vars + + +``` validate-scenario +set-vars, + [playback-time=(double,string)]; +``` + +Define vars to be used in other actions. +For example you can define vars for buffer checksum to be used in the "check-last-sample" action type as follow: + +``` + set-vars, frame1=SomeRandomHash1,frame2=Anotherhash... + check-last-sample, checksum=frame1 +``` + + * Implementer namespace: core + +### Parameters + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## set-property + + +``` validate-scenario +set-property, + property-name=(string), + property-value=(The same type of @property-name), + [target-element-factory-name=(string)], + [target-element-klass=(string)], + [target-element-name=(string)], + [playback-time=(double,string)]; +``` + +Sets a property of an element or klass of elements in the pipeline. +Besides property-name and value, either 'target-element-name' or +'target-element-klass' needs to be defined + * Implementer namespace: core + +### Parameters + +* `property-name`:(mandatory): The name of the property to set on @target-element-name + + Possible types: `string` + +* `property-value`:(mandatory): The value of @property-name to be set on the element + + Possible types: `The same type of @property-name` + +* `target-element-factory-name`:(optional): The name factory for which to set a property on built elements + + Possible types: `string` + + Default: (null) + +* `target-element-klass`:(optional): The klass of the GstElements to set a property on + + Possible types: `string` + + Default: (null) + +* `target-element-name`:(optional): The name of the GstElement to set a property on + + Possible types: `string` + + Default: (null) + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + optional : Don't raise an error if this action hasn't been executed or failed + ### Possible types: + boolean + Default: false + +## check-property + + +``` validate-scenario +check-property, + property-name=(string), + property-value=(The same type of @property-name), + [target-element-factory-name=(string)], + [target-element-klass=(string)], + [target-element-name=(string)], + [playback-time=(double,string)]; +``` + +Check the value of property of an element or klass of elements in the pipeline. +Besides property-name and value, either 'target-element-name' or +'target-element-klass' needs to be defined + * Implementer namespace: core + +### Parameters + +* `property-name`:(mandatory): The name of the property to set on @target-element-name + + Possible types: `string` + +* `property-value`:(mandatory): The expected value of @property-name + + Possible types: `The same type of @property-name` + +* `target-element-factory-name`:(optional): The name factory for which to check a property value on built elements + + Possible types: `string` + + Default: (null) + +* `target-element-klass`:(optional): The klass of the GstElements to check a property on + + Possible types: `string` + + Default: (null) + +* `target-element-name`:(optional): The name of the GstElement to check a property value + + Possible types: `string` + + Default: (null) + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## set-debug-threshold + + +``` validate-scenario +set-debug-threshold, + debug-threshold=(string), + [playback-time=(double,string)]; +``` + +Sets the debug level to be used, same format as +setting the GST_DEBUG env variable + * Implementer namespace: core + +### Parameters + +* `debug-threshold`:(mandatory): String defining debug threshold +See gst_debug_set_threshold_from_string + + Possible types: `string` + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## emit-signal + + +``` validate-scenario +emit-signal, + signal-name=(string), + target-element-name=(string), + [playback-time=(double,string)]; +``` + +Emits a signal to an element in the pipeline + * Implementer namespace: core + +### Parameters + +* `signal-name`:(mandatory): The name of the signal to emit on @target-element-name + + Possible types: `string` + +* `target-element-name`:(mandatory): The name of the GstElement to emit a signal on + + Possible types: `string` + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## appsrc-push + + +``` validate-scenario +appsrc-push, + file-name=(string), + target-element-name=(string), + [caps=(caps)], + [offset=(uint64)], + [size=(uint64)], + [playback-time=(double,string)]; +``` + +Queues a buffer in an appsrc. If the pipeline state allows flow of buffers, the next action is not run until the buffer has been pushed. + * Implementer namespace: core + +### Parameters + +* `file-name`:(mandatory): Relative path to a file whose contents will be pushed as a buffer + + Possible types: `string` + +* `target-element-name`:(mandatory): The name of the appsrc to push data on + + Possible types: `string` + +* `caps`:(optional): Caps for the buffer to be pushed + + Possible types: `caps` + + Default: (null) + +* `offset`:(optional): Offset within the file where the buffer will start + + Possible types: `uint64` + + Default: (null) + +* `size`:(optional): Number of bytes from the file that will be pushed as a buffer + + Possible types: `uint64` + + Default: (null) + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## appsrc-eos + + +``` validate-scenario +appsrc-eos, + target-element-name=(string), + [playback-time=(double,string)]; +``` + +Queues a EOS event in an appsrc. + * Implementer namespace: core + +### Parameters + +* `target-element-name`:(mandatory): The name of the appsrc to emit EOS on + + Possible types: `string` + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## flush + + +``` validate-scenario +flush, + target-element-name=(string), + [reset-time=(boolean)], + [playback-time=(double,string)]; +``` + +Sends FLUSH_START and FLUSH_STOP events. + * Implementer namespace: core + +### Parameters + +* `target-element-name`:(mandatory): The name of the appsrc to flush on + + Possible types: `string` + +* `reset-time`:(optional): Whether the flush should reset running time + + Possible types: `boolean` + + Default: TRUE + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## disable-plugin + + +``` validate-scenario +disable-plugin, + plugin-name=(string), + [as-config=(boolean)], + [playback-time=(double,string)]; +``` + +Disables a GstPlugin + * Implementer namespace: core + +### Parameters + +* `plugin-name`:(mandatory): The name of the GstPlugin to disable + + Possible types: `string` + +* `as-config`:(optional): Execute action as a config action (meaning when loading the scenario) + + Possible types: `boolean` + + Default: false + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## check-last-sample + + +``` validate-scenario +check-last-sample, + [checksum=(string)], + [sink-factory-name=(string)], + [sink-name=(string)], + [sinkpad-caps=(string)], + [timecode-frame-number=(string)], + [playback-time=(double,string)]; +``` + +Checks the last-sample checksum or frame number (set on its GstVideoTimeCodeMeta) on declared Sink element. This allows checking the checksum of a buffer after a 'seek' or after a GESTimeline 'commit' for example + * Implementer namespace: core + +### Parameters + +* `checksum`:(optional): The reference checksum of the buffer. + + Possible types: `string` + + Default: (null) + +* `sink-factory-name`:(optional): The name of the factory of the sink element to check sample on. + + Possible types: `string` + + Default: (null) + +* `sink-name`:(optional): The name of the sink element to check sample on. + + Possible types: `string` + + Default: (null) + +* `sinkpad-caps`:(optional): The caps (as string) of the sink to check. + + Possible types: `string` + + Default: (null) + +* `timecode-frame-number`:(optional): The frame number of the buffer as specified on its GstVideoTimeCodeMeta + + Possible types: `string` + + Default: (null) + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## crank-clock + + +``` validate-scenario +crank-clock, + [checksum=(string)], + [expected-elapsed-time=(GstClockTime)], + [expected-time=(GstClockTime)], + [sinkpad-caps=(string)], + [timecode-frame-number=(string)], + [playback-time=(double,string)]; +``` + +Crank the clock, possibly checking how much time was supposed to be waited on the clock and/or the clock running time after the crank. Using one `crank-clock` action in a scenario implies that the scenario is driving the clock and a #GstTestClock will be used. The user will need to crank it the number of time required (using the `repeat` parameter comes handy here). + * Implementer namespace: core + +### Parameters + +* `checksum`:(optional): The reference checksum of the buffer. + + Possible types: `string` + + Default: (null) + +* `expected-elapsed-time`:(optional): Check time elapsed during the clock cranking + + Possible types: `GstClockTime` + + Default: (null) + +* `expected-time`:(optional): Expected clock time after cranking + + Possible types: `GstClockTime` + + Default: (null) + +* `sinkpad-caps`:(optional): The caps (as string) of the sink to check. + + Possible types: `string` + + Default: (null) + +* `timecode-frame-number`:(optional): The frame number of the buffer as specified on its GstVideoTimeCodeMeta + + Possible types: `string` + + Default: (null) + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## video-request-key-unit + + +``` validate-scenario +video-request-key-unit, + direction=(string), + [all-headers=(boolean)], + [count=(int)], + [pad=(string)], + [running-time=(double or string)], + [srcpad=(string)], + [target-element-factory-name=(string)], + [target-element-klass=(string)], + [target-element-name=(string)], + [playback-time=(double,string)]; +``` + +Request a video key unit + * Implementer namespace: core + +### Parameters + +* `direction`:(mandatory): The direction for the event to travel, should be in + * [upstream, downstream] + + Possible types: `string` + +* `all-headers`:(optional): TRUE to produce headers when starting a new key unit + + Possible types: `boolean` + + Default: FALSE + +* `count`:(optional): integer that can be used to number key units + + Possible types: `int` + + Default: 0 + +* `pad`:(optional): The name of the GstPad to send a send force-key-unit to + + Possible types: `string` + + Default: sink + +* `running-time`:(optional): The running_time can be set to request a new key unit at a specific running_time. +If not set, GST_CLOCK_TIME_NONE will be used so upstream elements will produce a new key unit as soon as possible. + + Possible variables: + + * position: The current position in the stream + + * duration: The duration of the stream + + Possible types: `double or string` + + Default: (null) + +* `srcpad`:(optional): The name of the GstPad to send a send force-key-unit to + + Possible types: `string` + + Default: src + +* `target-element-factory-name`:(optional): The factory name of the GstElements to send a send force-key-unit to + + Possible types: `string` + + Default: (null) + +* `target-element-klass`:(optional): The klass of the GstElements to send a send force-key-unit to + + Possible types: `string` + + Default: Video/Encoder + +* `target-element-name`:(optional): The name of the GstElement to send a send force-key-unit to + + Possible types: `string` + + Default: (null) + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## check-position + + +``` validate-scenario +check-position, + expected-position=(GstClockTime), + [all-headers=(boolean)], + [count=(int)], + [pad=(string)], + [running-time=(double or string)], + [srcpad=(string)], + [target-element-factory-name=(string)], + [target-element-klass=(string)], + [target-element-name=(string)], + [playback-time=(double,string)]; +``` + +Check current pipeline position. + + * Implementer namespace: core + +### Parameters + +* `expected-position`:(mandatory): The expected pipeline position + + Possible types: `GstClockTime` + +* `all-headers`:(optional): TRUE to produce headers when starting a new key unit + + Possible types: `boolean` + + Default: FALSE + +* `count`:(optional): integer that can be used to number key units + + Possible types: `int` + + Default: 0 + +* `pad`:(optional): The name of the GstPad to send a send force-key-unit to + + Possible types: `string` + + Default: sink + +* `running-time`:(optional): The running_time can be set to request a new key unit at a specific running_time. +If not set, GST_CLOCK_TIME_NONE will be used so upstream elements will produce a new key unit as soon as possible. + + Possible variables: + + * position: The current position in the stream + + * duration: The duration of the stream + + Possible types: `double or string` + + Default: (null) + +* `srcpad`:(optional): The name of the GstPad to send a send force-key-unit to + + Possible types: `string` + + Default: src + +* `target-element-factory-name`:(optional): The factory name of the GstElements to send a send force-key-unit to + + Possible types: `string` + + Default: (null) + +* `target-element-klass`:(optional): The klass of the GstElements to send a send force-key-unit to + + Possible types: `string` + + Default: Video/Encoder + +* `target-element-name`:(optional): The name of the GstElement to send a send force-key-unit to + + Possible types: `string` + + Default: (null) + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## corrupt-socket-recv + + +``` validate-scenario +corrupt-socket-recv, + errno=(string), + port=(int), + [times=(int)], + [playback-time=(double,string)]; +``` + +corrupt the next socket receive + * Implementer namespace: validatefaultinjection + +### Parameters + +* `errno`:(mandatory): errno to set when failing + + Possible types: `string` + +* `port`:(mandatory): The port the socket to be corrupted listens on + + Possible types: `int` + +* `times`:(optional): Number of times to corrupt recv, default is one + + Possible types: `int` + + Default: 1 + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## gtk-put-event + + +``` validate-scenario +gtk-put-event, + [keys=(string)], + [string=(string)], + [type=(string)], + [widget-name=(string)], + [playback-time=(double,string)]; +``` + +Put a GdkEvent on the event list using gdk_put_event + * Implementer namespace: validategtk + +### Parameters + +* `keys`:(optional): The keyboard keys to be used for the event, parsed with gtk_accelerator_parse_with_keycode, so refer to its documentation for more information + + Possible types: `string` + + Default: (null) + +* `string`:(optional): The string to be 'written' by the keyboard sending KEY_PRESS GdkEvents + + Possible types: `string` + + Default: (null) + +* `type`:(optional): The event type to get executed. the string should look like the ones in GdkEventType but without the leading 'GDK_'. It is not mandatory as it can be computed from other present fields (e.g, an action with 'keys' will consider the type as 'key_pressed' by default). + + Possible types: `string` + + Default: (null) + +* `widget-name`:(optional): The name of the target GdkWidget of the GdkEvent. That widget has to contain a GdkWindow. If not specified, the event will be sent to the first toplevel window + + Possible types: `string` + + Default: (null) + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) + +## set-subtitle + + +``` validate-scenario +set-subtitle, + subtitle-file=(string (A URI)), + [playback-time=(double,string)]; +``` + +Action to set a subtitle file to use on a playbin pipeline. +The subtitles file that will be used should be specified +relative to the playbin URI in use thanks to the subtitle-file +action property. You can also specify a folder with subtitle-dir +For example if playbin.uri='file://some/uri.mov' +and action looks like 'set-subtitle, subtitle-file=en.srt' +the subtitle URI will be set to 'file:///some/uri.mov.en.srt' + + * Implementer namespace: validate-launcher + +### Parameters + +* `subtitle-file`:(mandatory): Sets a subtitles file on a playbin pipeline + + Possible types: `string (A URI)` + +* `playback-time`:(optional): The playback time at which the action will be executed + + Possible variables: + + * `position`: The current position in the stream + + * `duration`: The duration of the stream + + Possible types: `double,string` + + Default: 0.0 + +* `on-message`:(optional): Specify on what message type the action will be executed. + If both 'playback-time' and 'on-message' is specified, the action will be executed + on whatever happens first. + + Possible types: `string` + + Default: (null) diff --git a/docs/gst-validate-config.md b/docs/gst-validate-config.md new file mode 100644 index 0000000000..90911d309a --- /dev/null +++ b/docs/gst-validate-config.md @@ -0,0 +1,110 @@ +--- +title: Configuration +short-description: GstValidate configuration +... + +# GstValidate Configuration + +GstValidate comes with some possible configuration files +to setup its plugins and core behaviour. The config format is very similar +to the [scenario](gst-validate-scenarios.md) file format. + +You can check the [ssim plugin](plugins/ssim.md) +and the [validate flow plugin](gst-validate-flow.md) +for examples. + +## Core settings parameters + +Config name should be `core`. + +### `verbosity` + +Default: `position` + +See [GstValidateVerbosityFlags](GstValidateVerbosityFlags) for possible values. + +### `action` + +The [action type](gst-validate-action-types.md) to execute, the action type +must be a CONFIG action or the action type must have a `as-config` argument. When the `action` +is specified in a parameter, a validate action is executed using the other parameters of the +config as configuration for the validate scenario action. + +#### Example: + +``` +GST_VALIDATE_CONFIG="core, action=set-property, target-element-name="videotestsrc0", property-name=pattern, property-value=blue" gst-validate-1.0 videotestsrc ! autovideosink +``` + +This will execute the `set-property, target-element-name="videotestsrc0", +property-name=pattern, property-value=blue` validate action directly from the +config file + +### `scenario-action-execution-interval` + +Default: `0` meaning that action are executed in `idle` callbacks. + +Set the interval between [GstValidateScenario](gst-validate-scenarios.md) actions execution. + +### `max-latency` + +Default: `GST_CLOCK_TIME_NONE` - disabled + +Set the maximum latency reported by the pipeline, over that defined latency the scenario will report +an `config::latency-too-high` issue. + +### `max-dropped` + +Default: `GST_CLOCK_TIME_NONE` - disabled + +The maximum number of dropped buffers, a `config::too-many-buffers-dropped` issue will be reported +if that limit is reached. + +### `fail-on-missing-plugin` + +Default: `false` meaning that tests are marked as skipped when a GStreamer plugin is missing. + +## Variables + +You can use variables in the configs the same way you can set them in +[gst-validate-scenarios](gst-validate-scenarios.md). + +Defaults variables are: + +- `$(TMPDIR)`: The default temporary directory as returned by `g_get_tmp_dir`. +- `$(CONFIG_PATH)`: The path of the running scenario. +- `$(CONFIG_DIR)`: The directory the running scenario is in. +- `$(CONFIG_NAME)`: The name of the config file +- `$(LOGSDIR)`: The directory where to place log files. This uses the + `GST_VALIDATE_LOGSDIR` environment variable if available or `$(TMPDIR)` if + the variables hasn't been set. (Note that the + [gst-validate-launcher](gst-validate-launcher.md) set the environment + variables). + +You can also set you own variables by using the `set-vars=true` argument: + +``` yaml +core, set-vars=true, log-path=$(CONFIG_DIR/../log) +``` + +It is also possible to set global variables (also usable from +[scenarios](gst-validate-scenarios.md)) with: + +``` yaml +set-globals, TESTSUITE_ROOT_DIR=$(CONFIG_DIR) +``` + +## `change-issue-severity` settings parameters + +You can change issues severity with the `change-issue-severity` configuration +with the following parameters: + +* `issue-id`: The GQuark name of the issue, for example: `event::segment-has-wrong-start`, + You can use `gst-validate-1.0 --print-issue-types` to list all issue types. +* `new-severity`: The new [`severity`](GstValidateReportLevel) of the issue +* `element-name` (*optional*): The name of the element the severity + change applies to +* `element-factory-name` (*optional*): The element factory name of the elements the + severity change applies to +* `element-classification` (*optional*): The classification of the elements the + severity change applies to diff --git a/docs/gst-validate-environment-variables.md b/docs/gst-validate-environment-variables.md new file mode 100644 index 0000000000..6cf48f9c3c --- /dev/null +++ b/docs/gst-validate-environment-variables.md @@ -0,0 +1,153 @@ +--- +title: Environment variables +short-description: Environment variables influencing runtime behaviour +... + +# GstValidate Environment Variables + +The runtime behaviour of GstValidate applications can be influenced by a +number of environment variables. + +**GST_VALIDATE.** + +This environment variable can be set to a list of debug options, which +cause GstValidate to print out different types of test result +information and consider differently the level of the reported issues. + +* `fatal-criticals`: Causes GstValidate to consider only critical issues as import enough + to consider the test failed (default behaviour) +* `fatal-warnings`: Causes GstValidate to consider warning, and critical issues as + import enough to consider the test failed +* `fatal-issues`: Causes GstValidate to consider issue, warning, and critical issues + as import enough to consider the test failed +* `print-issues`: Causes GstValidate to print issue, warning and critical issues in + the final reports (default behaviour) +* `print-warnings`: Causes GstValidate to only print warning and critical issues in the + final reports +* `print-criticals`: Causes GstValidate to only print critical issues in the final + reports + +**GST_VALIDATE_FILE.** + +Set this variable to a colon-separated list of paths to redirect all +GstValidate messages to this file. If left unset, debug messages will be +outputed into the standard error. + +You can use the special names `stdout` and `stderr` to use those output. + +**GST_VALIDATE_SCENARIOS_PATH.** + +Set this variable to a colon-separated list of paths. GstValidate will +scan these paths for GstValidate scenario files. By default GstValidate +will look for scenarios in the user data directory as specified in the +[XDG standard]: +`.local/share/gstreamer-GST_API_VERSION/validate/scenarios` and the +system wide user data directory: +`/usr/lib/gstreamer-GST_API_VERSION/validate/scenarios` + +**GST_VALIDATE_CONFIG.** + +Set this variable to a colon-separated list of paths to GstValidate +config files or directly as a string in the GstCaps serialization +format. The config file has a format similar to the scenario file. The +name of the configuration corresponds to the name of the plugin the +configuration applies to. + +The special name "core" is used to configure GstValidate core +functionalities (monitors, scenarios, etc...). + +If you want to make sure to set a property on a element of a type (for +example to disable QoS on all sinks) you can do: + +``` +core, action=set-property, target-element-klass=Sink +``` + +If you want the GstPipeline to get dumped when an issue of a certain +level (and higher) happens, you can do: + +``` +core, action=dot-pipeline, report-level=issue +``` + +Note that you will still need to set GST_DEBUG_DUMP_DOT_DIR. + +For more examples you can look at the ssim GstValidate plugin +documentation to see how to configure that plugin. + +You can also check that a src pad is pushing buffers at a minimum +frequency. For example to check if v4l2src is producing at least 60 frames +per second you can do: + +``` yaml + core,min-buffer-frequency=60,target-element-factory-name=v4l2src +``` + +This config accepts the following fields: +- `min-buffer-frequency`: the expected minimum rate, in buffers per + second, at which buffers are pushed on the pad + +- `target-element-{factory-name,name,klass}`: the factory-name, object + name or class of the element to check + +- `name`: (optional) only check the frequency if the src pad has this + name + +- `buffer-frequency-start`: (optional) if defined, validate will + ignore the frequency of the pad during the time specified in this + field, in ns. This can be useful when testing live pipelines where + configuring and setting up elements can take some time slowing down + the first buffers until the pipeline reaches its cruising speed. +**GST_VALIDATE_OVERRIDE.** + +Set this variable to a colon-separated list of dynamically linkable +files that GstValidate will scan looking for overrides. By default +GstValidate will look for scenarios in the user data directory as +specified in the [XDG standard]: +`.local/share/gstreamer-GST_API_VERSION/validate/scenarios` and the +system wide user data directory: +`/usr/lib/gstreamer-GST_API_VERSION/validate/scenarios` + +**GST_VALIDATE_SCENARIO_WAIT_MULITPLIER.** + +A decimal number to set as a multiplier for the wait actions. For +example if you set `GST_VALIDATE_SCENARIO_WAIT_MULITPLIER=0.5`, for a +wait action that has a duration of 2.0 the waiting time will only be of +1.0 second. If set to 0, wait action will be ignored. + +**GST_VALIDATE_REPORTING_DETAILS.** + +The reporting level can be set through the +GST_VALIDATE_REPORTING_DETAILS environment variable, as a +comma-separated list of (optional) object categories / names and levels. +Omit the object category / name to set the global level. + +Examples: + +``` +GST_VALIDATE_REPORTING_DETAILS=synthetic,h264parse:all +GST_VALIDATE_REPORTING_DETAILS=none,h264parse::sink_0:synthetic +``` + +Levels being: + +* `none`: No debugging level specified or desired. Used to deactivate + debugging output. +* `synthetic`: Summary of the issues found, with no details. +* `subchain`: If set as the default level, similar issues can be reported multiple + times for different subchains. If set as the level for a particular + object (`my_object:subchain`), validate will report the issues where + the object is the first to report an issue for a subchain. +* `monitor`: If set as the default level, all the distinct issues for all the + monitors will be reported. If set as the level for a particular + object, all the distinct issues for this object will be reported. + Note that if the same issue happens twice on the same object, up + until this level that issue is only reported once. +* `all`: All the issues will be reported, even those that repeat themselves + inside the same object. This can be **very** verbose if set + globally. + +Setting the reporting level allows to control the way issues are +reported when calling [gst_validate_runner_printf()](gst_validate_runner_printf). + + [XDG standard]: http://www.freedesktop.org/wiki/Software/xdg-user-dirs/ diff --git a/docs/gst-validate-flow.md b/docs/gst-validate-flow.md new file mode 100644 index 0000000000..6e67f0ecc7 --- /dev/null +++ b/docs/gst-validate-flow.md @@ -0,0 +1,196 @@ +--- +title: Validate Flow +short-description: Validate a pad data flow +... + +# Validate Flow + +Validate Flow — GStreamer validate component to record a log of buffers and +events flowing in a specified pad and compare it with an expectation file. + +## Description + +This component exists for the purpose of testing non-regular-playback use cases +where the test author specifies the full pipeline, a series of actions and needs +to check whether the generated buffers and events make sense. + +The testing procedure goes like this: + +1. The test author writes a [.validatetest](gst-validate-test-file.md) test + where validateflow is used. A pad where monitoring will occur is specified + and possibly a list of [actions](gst-validate-action-types.md) to run can + also be specified. + +2. The test author runs the test with the desired pipeline, the configuration + and the actions. Since an expectation file does not exist at + this point, validateflow will create one. The author should check its + contents for any missing or unwanted events. No actual checking is done by + validateflow in this step, since there is nothing to compare to yet. + +3. Further executions of the test will also record the produced buffers and + events, but now they will be compared to the previous log (expectation file). + Any difference will be reported as a test failure. The original expectation + file is never modified by validateflow. Any desired changes can be made by + editing the file manually or deleting it and running the test again. + +## Example + +### Simplest example + +The following is an example of a `fakesrc.simple.validatetest` file using +validateflow. + +{{ fakesrc.simple.validatetest.ini }} + +Then generate the expectation file with: + +``` bash +gst-validate-1.0 --set-test-file /path/to/fakesrc.simple.validatetest +``` + +This will generate the +`/path/to/fakesrc.simple/flow-expectations/log-sink-sink-expected` file +containing: + +{{ plugins/fakesrc.simple/flow-expectations/log-sink-sink-expected.log }} + +Note that the test will be marked as "SKIPPED" when we generate expectation +files. + +The test can now be run with: + +``` +gst-validate-1.0 --set-test-file /path/to/fakesrc.simple.validatetest +``` + +### Example controlling the source + +The following is an example of the `qtdemux_change_edit_list.validatetest` file using validateflow. + +``` ini +set-globals, media_dir="$(test_dir)/../../../medias/" +meta, + seek=false, + handles-states=false, + args = { + "appsrc ! qtdemux ! fakesink async=false", + }, + configs = { + "$(validateflow), pad=fakesink0:sink, record-buffers=false", + } + +# Scenario action types +appsrc-push, target-element-name=appsrc0, file-name="$(media_dir)/fragments/car-20120827-85.mp4/init.mp4" +appsrc-push, target-element-name=appsrc0, file-name="$(media_dir)/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="$(media_dir)/fragments/car-20120827-86.mp4/init.mp4" +appsrc-push, target-element-name=appsrc0, file-name="$(media_dir)/fragments/car-20120827-86.mp4/media2.mp4" +stop +``` + +This example shows the elements of a typical validate flow test (a pipeline, a +config and a scenario). Some actions typically used together with validateflow +can also be seen. Notice variable interpolation is used to fill absolute paths +for media files in the scenario (`$(test_dir)`). In the configuration, +`$(validateflow)` is expanded to something like this, containing proper paths +for expectations and actual results (these values are interpolated from the +`.validatetest` file location): + +``` ini +validateflow, expectations-dir="/validate/test/file/path/validateqtdemux_change_edit_list/flow-expectations/", actual-results-dir="$(GST_VALIDATE_LOGSDIR)/logs/validate/launch_pipeline/qtdemux_change_edit_list" +``` + +The resulting log looks like this: + +``` ini +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; +``` + +## Configuration + +In order to use the plugin a validate configuration must be provided, +containing a line starting by `validateflow` followed by a number of settings. +Every `validateflow` line creates a `ValidateFlowOverride`, which listens to a +given pad. A test may have several `validateflow` lines, therefore having +several overrides and listening to different pads with different settings. + +* `pad`: Required. Name of the pad that will be monitored. +* `record-buffers`: Default: false. Whether buffers will be logged. By default + only events are logged. +* `buffers-checksum`: Default: 'none'. Define the type of checksums to be used + valid values are: + * `none`: No checksum recorded + * `as-id`: Record checksum as 'ids' where the IDs are incremented on each new + checksum passed in + * `md5`: md5 checksum + * `sha1`: sha1 checksum + * `sha256`: sha256 checksum + * `sha512`: sha512 checksum + * *Note*: for backward compatibility reasons, this can be passed as a + boolean and it will default to 'sha1' if true, 'none' if false. +* `ignored-fields`: Default: `"stream-start={ stream-id }"` (as they are often + non reproducible). Key with a serialized GstValueList(str) of fields to not + record. +* `logged-fields`: Default: `NULL` Key with a serialized GstValueList(str) of + fields to record, eg. `logged-event-fields="stream-start={flags}, + caps={width, height, framerate}, buffer={pts}"`. Overrides + `ignored-event-fields` for specified event types. +* `ignored-event-types`: Default: `{ }`. List of event type names to not record +* `logged-event-types`: Default: `NULL`. List of event type names to not record, + if noone provided, all events are logged, except the ones defined in the + `ignored-event-types`. +* `expectations-dir`: Path to the directory where the expectations will be + written if they don't exist, relative to the current working directory. By + default the current working directory is used, but this setting is usually + set automatically as part of the `%(validateflow)s` expansion to a correct + path like `~/gst-validate/gst-integration-testsuites/flow-expectations/`. +* `actual-results-dir`: Path to the directory where the events will be recorded. + The expectation file will be compared to this. By default the current working + directory is used, but this setting is usually set automatically as part of + the `%(validateflow)s` expansion to the test log directory, i.e. + `~/gst-validate/logs/validate/launch_pipeline/`. +* `generate-expectations`: Default: unset. When set to `true` the expectation + file will be written and no testing will be done and if set to `false`, the + expectation file will be required. If a validateflow config is used without + specifying any other parametters, the validateflow plugin will consider that + all validateflow overrides will use that value. + + +## Scenario actions + +Scenarios with validateflow work in the same way as other tests. Often +validatetests will use appsrc in order to control the flow of data precisely, +possibly interleaving events in between. The following is a list of useful +actions. + + * `appsrc-push`: Pushes a buffer from an appsrc element and waits for the chain + operation to finish. A path to a file is provided, optionally with an offset + and/or size. + * `appsrc-eos`: Queues an EOS event from the appsrc. The action finishes + immediately at this point. + * `stop`: Tears down the pipeline and stops the test. + * `checkpoint`: Records a "checkpoint" message in all validateflow overrides, + with an optional explanation message. This is useful to check certain events + or buffers are sent at a specific moment in the scenario, and can also help + to the comprehension of the scenario. + +More details on these actions can be queried from the command line, like this: + +``` bash +gst-validate-1.0 --inspect-action-type appsrc-push +``` diff --git a/docs/gst-validate-launcher.md b/docs/gst-validate-launcher.md new file mode 100644 index 0000000000..b30db1558c --- /dev/null +++ b/docs/gst-validate-launcher.md @@ -0,0 +1,166 @@ +--- +short-description: Integration testsuite builder and launcher +... + +# gst-validate-launcher + +`gst-validate-launcher` is an application to run unit or integration testsuites +providing a set of options and features to help debugging them. + +## Run the GStreamer unit tests + +Running GStreamer unit tests inside `gst-build` is as simple as doing: + +``` +gst-validate-launcher check.gst* +``` + +If you only want to run GStreamer core tests: + +``` +gst-validate-launcher check.gstreamer* +``` + +Or to run unit tests from gst-plugins-base + +``` +gst-validate-launcher check.gst-plugins-base +``` + +You can also run them inside valgrind with the `-vg` option or inside gdb with +`--gdb` for example. + +## Run the GstValidate default testsuite + +GstValidate comes with a default testsuite to be executed on a default +set of media samples. Those media samples are stored with `git-lfs` so +you will need it to be able to launch the default testsuite. + +We recommend using `gst-build` to setup everything needed to run the testsuite +and you can simply do: + + gst-validate-launcher validate + +This will only launch the GstValidate tests and not other applications +that might be supported (currently `ges-launch` is also supported and +has its own default testsuite). + +## Example of a testsuite implementation + +To implement a testsuite, you will have to write some simple python code +that defines the tests to be launched by `gst-validate-launcher`. + +In this example, we will assume that you want to write a whole new +testsuite based on your own media samples and [scenarios](GstValidateScenario). The +set of media files and the testsuite implementation file will be +structured as follow: + + testsuite_folder/ + |-> testsuite.py + |-> sample_files/ + |-> file.mp4 + |-> file1.mkv + |-> file2.ogv + |-> scenarios + |-> scenario.scenario + |-> scenario1.scenario + +You should generate the `.media_info` files. To generate them for local +files, you can use: + + gst-validate-launcher --medias-paths /path/to/sample_files/ --generate-media-info + +For remote streams, you should use +`gst-validate-media-check-1.0`. For an http stream you can +for example do: + + gst-validate-media-check-GST_API_VERSION http://someonlinestream.com/thestream \ + --output-file /path/to/testsuite_folder/sample_files/thestream.stream_info + + +The `gst-validate-launcher` will use the generated `.media_info` and +`.stream_info` files to validate the tests as those contain the +necessary information. + +Then you will need to write the `testsuite.py` file. You can for example +implement the following testsuite: + +``` python +""" +The GstValidate custom testsuite +""" + +import os +from launcher.baseclasses import MediaFormatCombination +from launcher.apps.gstvalidate import * +TEST_MANAGER = "validate" + +KNOWN_ISSUES = {} + +def setup_tests(test_manager, options): + print("Setting up the custom testsuite") + assets_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".", "samples_files")) + options.add_paths(assets_dir) + + # This step will register default data for the test manager: + # - scenarios such as `play_15s`, `reverse_playback` etc. + # - encoding formats such as "mp4,h264,mp3" etc. + # - blacklist such as dash.media_check.* + # - test generators: + # - GstValidatePlaybinTestsGenerator + # - GstValidateMediaCheckTestsGenerator + # - GstValidateTranscodingTestsGenerator + # This 'defaults' can be found in 'gst-devtools/validate/launcher/apps/gstvalidate.py#register_defaults' + # test_manager.register_defaults() + + # Add scenarios + scenarios = [] + scenarios.append("play_5s") + scenarios.append("seek_backward") + test_manager.set_scenarios(scenarios) + + # Add encoding formats used by the transcoding generator + test_manager.add_encoding_formats([ + MediaFormatCombination("mp4", "mp3", "h264"),]) + + # Add generators + # GstValidatePlaybinTestsGenerator needs at least one media file + test_manager.add_generators([GstValidateMediaCheckTestsGenerator(test_manager)]) + # GstValidatePlaybinTestsGenerator needs at least one scenario + test_manager.add_generators([GstValidatePlaybinTestsGenerator(test_manager)]) + # GstValidateTranscodingTestsGenerator needs at least one MediaFormatCombination + test_manager.add_generators([GstValidateTranscodingTestsGenerator(test_manager)]) + + # list of combo to blacklist tests. Here it blacklists all tests with playback.seek_backward + test_manager.set_default_blacklist([ + ("custom_testsuite.file.playback.seek_backward.*", + "Not supported by this testsuite."),]) + + # you can even pass known issues to bypass an existing error in your custom testsuite + test_manager.add_expected_issues(KNOWN_ISSUES) + return True +``` + +Once this is done, you've got a testsuite that will: + +- Run playbin pipelines on `file.mp4`, `file1.mkv` and `file2.ogv`> + executing `play_5s` and `seek_backward` scenarios + +- Transcode `file.mp4,` `file1.mkv` and `file2.ogv` to h264 and + mp3 in a MP4 container + +The only thing to do to run the testsuite is: + + + gst-validate-launcher --testsuites-dir=/path/to/testsuite_folder/ testsuite + + +# Invocation + +You can find detailed information about the launcher by launching it: + + gst-validate-launcher --help + +You can list all the tests with: + + gst-validate-launcher --testsuites-dir=/path/to/testsuite_folder/ testsuite -L diff --git a/docs/gst-validate-media-check.md b/docs/gst-validate-media-check.md new file mode 100644 index 0000000000..d99c88779e --- /dev/null +++ b/docs/gst-validate-media-check.md @@ -0,0 +1,32 @@ +--- +short-description: Tool to test GStreamer media types discovery +... + +# gst-validate-media-check + +`gst-validate-media-check` is command line tool checking that media +files discovering works properly with `gst-discoverer` over multiple +runs. It needs a reference text file containing valid information about +a media file (which can be generated with the same tool) and then it +will be able to check that the reference matches what will be reported +by `gst-discoverer` in the following runs. + +For example, given that we have a valid `reference.media_info` file, we +can run: + + gst-validate-media-check-GST_API_VERSION file:///./file.ogv --expected-results reference.media_info + +It will then output any error encountered and return an exit code +different from 0 if any error is found. + +# Invocation + +`gst-validate-media-check` takes an URI to analyze and some extra +options to control the output. + +## Options + +* `-o`, `--output-file`: The output file to store the results. +* `-f`, `--full`: Fully analize the file frame by frame. +* `-e`, `--expected-results`: Path to file containing the expected results (or the last results + found) for comparison with new results. diff --git a/docs/gst-validate-scenarios.md b/docs/gst-validate-scenarios.md new file mode 100644 index 0000000000..af25217692 --- /dev/null +++ b/docs/gst-validate-scenarios.md @@ -0,0 +1,110 @@ +--- +title: Scenarios +short-description: The GstValidate Scenario format +... + +# GstValidate Scenario File Format + +To be able to define a list of actions to execute on a [`GstPipeline`], +a dedicated file format is used. The name of the scenario is the name of +the file without its `.scenario` extension. The scenario file format is +based on the [`GstStructure`] serialized format which is a basic, type +aware, key value format. It takes the type of the action in the first +comma separated field, and then some key value pairs in the form +`parameter=value` separated by commas. The values type will be guessed +if not casted as in `parameter=(string)value`. You can force the type +guessing system to actually know what type you want by giving it the +right hints. For example to make sure the value is a double, you should +add a decimal (ie. `1` will be considered as a `int`, but `1.0` will be +considered as a `double` and `"1.0"` will be considered as a `string`). + +For example to represent a seek action, you should add the following +line in the `.scenario` file. + + seek, playback-time=10.0, start=0.0, flags=accurate+flush + +The files to be used as scenario should have a `.scenario` extension and +should be placed either in +`$USER_DATA_DIR/gstreamer-1.0/validate/scenarios` , +`$GST_DATADIR/gstreamer-1.0/validate/scenarios` or in a path defined in +the \$GST\_VALIDATE\_SCENARIOS\_PATH environment variable. + +Each line in the `.scenario` file represent an action (you can also use +`\ ` at the end of a line write a single action on multiple lines). +Usually you should start you scenario with a `meta` structure +in order for the user to have more information about the +scenario. It can contain a `summary` field which is a string explaining +what the scenario does and then several info fields about the scenario. +You can find more info about it running: + + gst-validate-1.0 --inspect-action-type action_type_name + +So a basic scenario file that will seek three times and stop would look +like: + +``` +meta, summary="Seeks at 1.0 to 2.0 then at \ +3.0 to 0.0 and then seeks at \ +1.0 to 2.0 for 1.0 second (between 2.0 and 3.0).", \ +seek=true, duration=5.0, min-media-duration=4.0 +seek, playback-time=1.0, rate=1.0, start=2.0, flags=accurate+flush +seek, playback-time=3.0, rate=1.0, start=0.0, flags=accurate+flush +seek, playback-time=1.0, rate=1.0, start=2.0, stop=3.0, flags=accurate+flush +``` + +Many action types have been implemented to help users define their own +scenarios. For example there are: + +- `seek`: Seeks into the stream. +- `play`: Set the pipeline state to `GST_STATE_PLAYING`. +- `pause`: Set the pipeline state to `GST_STATE_PAUSED`. +- `stop`: Stop the execution of the pipeline. + +> **NOTE**: This action actually posts a [`GST_MESSAGE_REQUEST_STATE`] +> message requesting [`GST_STATE_NULL`] on the bus and the application +> should quit. + +To get all the details about the registered action types, you can list +them all with: + +``` +gst-validate-1.0 --inspect-action-type +``` + +and to include transcoding specific action types: + +``` +gst-validate-transcoding-1.0 --inspect-action-type +``` + +Many scenarios are distributed with `gst-validate`, you can list them +all using: + +``` +gst-validate-1.0 --list-scenarios +``` + +You can find more information about the scenario implementation and +action types in the [`GstValidateScenario` section]. + + [`GstPipeline`]: GstPipeline + [`GstStructure`]: GstStructure + [`GST_MESSAGE_REQUEST_STATE`]: GST_MESSAGE_REQUEST_STATE + [`GST_STATE_NULL`]: GST_STATE_NULL + [`GstValidateScenario` section]: GstValidateScenario + +## Default variables + +Any action can use the default variables: + +- `$(position)`: The current position in the pipeline as reported by + [gst_element_query_position()](gst_element_query_position) +- `$(duration)`: The current duration of the pipeline as reported by + [gst_element_query_duration()](gst_element_query_duration) +- `$(TMPDIR)`: The default temporary directory as returned by `g_get_tmp_dir`. +- `$(SCENARIO_PATH)`: The path of the running scenario. +- `$(SCENARIO_DIR)`: The directory the running scenario is in. +- `$(SCENARIO_NAME)`: The name the running scenario + + +It is also possible to set variables in scenario with the `set-vars` action. \ No newline at end of file diff --git a/docs/gst-validate-test-file.md b/docs/gst-validate-test-file.md new file mode 100644 index 0000000000..c743618583 --- /dev/null +++ b/docs/gst-validate-test-file.md @@ -0,0 +1,118 @@ +--- +title: Test file +short-description: GstValidate test file +... + +# GstValidate Test file + +A `.validatetest` file describes a fully contained validate test case. It +includes the arguments of the tool supposed to be used to run the test as well +as possibly a [configuration](gst-validate-config.md) and a set of action to +describe the validate [scenario](gst-validate-scenarios.md). + +# The file format + +A validate test file requires a `meta` structure which contains the same +information as the [scenario](gst-validate-scenarios.md) `meta` with some +additional fields described below. The `meta` structure should be either the +first or the one following the `set-globals` structure. The `set-globals` +structures allows you to set global variables for the rest of the +`.validatetest` file and is a free form variables setter. For example you can +do: + +``` yaml +set-globals, media_dir=$(test_dir)/../../media +``` + +## Tool arguments + +In the case of [`gst-validate`](gst-validate.md) it **has to** contain an +`args` field with `gst-validate` argv arguments like: + +``` yaml +# This is the default tool so it is not mandatory for the `gst-validate` tool +tool = "gst-validate-$(gst_api_version)", +args = { + # pipeline description + videotestrc num-buffers=2 ! $(videosink), + # Random extra argument + --set-media-info $(test-dir)/some.media_info +} +``` + +## configs + +The `configs` field is an array of structures containing the same content as +usual [configs](gst-validate-config.md) files. + +For example: + +``` yaml +configs = { + # Set videotestsrc0 pattern value to `blue` + "core, action=set-property, target-element-name=videotestsrc0, property-name=pattern, property-value=blue", + "$(validateflow), pad=sink1:sink, caps-properties={ width, height };", +} +``` + +Note: Since this is GstStructure synthax, we need to have the structures in the +array as strings/within quotes. + +## expected-issues + +The `expected-issues` field is an array of `expected-issue` structures containing +information about issues to expect (which can be known bugs or not). + +Use `gst-validate-1.0 --print-issue-types` to print information about all issue types. + +For example: + +``` yaml +expected-issues = { + "expected-issue, issue-id=scenario::not-ended", +} +``` + +Note: Since this is GstStructure synthax, we need to have the structures in the +array as strings/within quotes. + +### Fields: + +* `issue-id`: (string): Issue ID - Mandatory if `summary` is not provided. +* `summary`: (string): Summary - Mandatory if `issue-id` is not provided. +* `details`: Regex string to match the issue details `detected-on`: (string): + The name of the element the issue happened on `level`: (string): + Issue level +* `sometimes`: (boolean): Default: `false` - Wheteher the issue happens only + sometimes if `false` and the issue doesn't happen, an error will + be issued. +* `issue-url`: (string): The url of the issue in the bug tracker if the issue is + a bug. + +### Variables + +The same way + +Validate testfile will define some variables to make those files relocable: + +* `$(test_dir)`: The directory where the `.validatetest` file is in. + +* `$(test_name)`: The name of the test file (without extension). + +* `$(test_name_dir)`: The name of the test directory (test_name with folder + separator instead of `.`). + +* `$(validateflow)`: The validateflow structure name with the default/right + values for the `expectations-dir` and `actual-results-dir` + fields. See [validateflow](gst-validate-flow.md) for more + information. + +* `$(videosink)`: The GStreamer videosink to use if the test can work with + different sinks for the video. It allows the tool to use + fakesinks when the user doesn't want to have visual feedback + for example. + +* `$(audiosink)`: The GStreamer audiosink to use if the test can work with + different sinks for the audio. It allows the tool to use + fakesinks when the user doesn't want to have audio feedback + for example. diff --git a/docs/gst-validate-transcoding.md b/docs/gst-validate-transcoding.md new file mode 100644 index 0000000000..419dca1878 --- /dev/null +++ b/docs/gst-validate-transcoding.md @@ -0,0 +1,126 @@ +--- +short-description: Tool to test GStreamer components +... + +# gst-validate-transcoding + +`gst-validate-transcoding` is tool to create media files transcoding +pipelines running inside the GstValidate monitoring infrastructure. + +You can for example transcode any media file to Vorbis audio + VP8 video +in a WebM container by doing: + + gst-validate-transcoding-GST_API_VERSION file:///./file.ogg file:///.../transcoded.webm -o 'video/webm:video/x-vp8:audio/x-vorbis' + +`gst-validate-transcoding` will list every issue encountered during the +execution of the transcoding operation in a human readable report like +the one below: + + issue : buffer is out of the segment range Detected on theoradec0.srcpad + at 0:00:00.096556426 Details : buffer is out of segment and shouldn't be + pushed. Timestamp: 0:00:25.000 - duration: 0:00:00.040 Range: + 0:00:00.000 - 0:00:04.520 Description : buffer being pushed is out of + the current segment's start-stop range. Meaning it is going to be + discarded downstream without any use + +The return code of the process will be 18 in case a `CRITICAL` issue has +been found. + +## The encoding profile serialization format + +This is the serialization format of a [GstEncodingProfile](GstEncodingProfile). + +Internally the transcoding application uses [GstEncodeBin](encodebin). +`gst-validate-transcoding-GST_API_VERSION` uses its own serialization +format to describe the [`GstEncodeBin.profile`](encodebin:profile) property of the +encodebin. + +The simplest serialized profile looks like: + + muxer_source_caps:videoencoder_source_caps:audioencoder_source_caps + +For example to encode a stream into a WebM container, with an OGG audio +stream and a VP8 video stream, the serialized [GstEncodingProfile](GstEncodingProfile) +will look like: + + video/webm:video/x-vp8:audio/x-vorbis + +You can also set the preset name of the encoding profile using the +caps+preset\_name syntax as in: + + video/webm:video/x-vp8+youtube-preset:audio/x-vorbis + +Moreover, you can set the [presence](gst_encoding_profile_set_presence) property +of an encoding profile using the `|presence` syntax as in: + + video/webm:video/x-vp8|1:audio/x-vorbis + +This field allows you to specify how many times maximum a +[GstEncodingProfile](GstEncodingProfile) can be used inside an encodebin. + +You can also use the `restriction_caps->encoded_format_caps` syntax to +specify the [restriction caps](GstEncodingProfile:restriction-caps) +to be set on a [GstEncodingProfile](GstEncodingProfile). It +corresponds to the restriction [GstCaps](GstCaps) to apply before the encoder +that will be used in the profile. The fields present in restriction caps +are properties of the raw stream (that is, before encoding), such as +height and width for video and depth and sampling rate for audio. This +property does not make sense for muxers. + +To force a video stream to be encoded with a Full HD resolution (using +WebM as the container format, VP8 as the video codec and Vorbis as the +audio codec), you should use: + + video/webm:video/x-raw,width=1920,height=1080->video/x-vp8:audio/x-vorbis + +### Some serialized encoding formats examples: + +MP3 audio and H264 in MP4: + +
+ + video/quicktime,variant=iso:video/x-h264:audio/mpeg,mpegversion=1,layer=3 + +
+ +Vorbis and theora in OGG: + +
+ + application/ogg:video/x-theora:audio/x-vorbis + +
+ +AC3 and H264 in MPEG-TS: + +
+ + video/mpegts:video/x-h264:audio/x-ac3 + +
+ +# Invocation + +`gst-validate-transcoding` takes and input URI and an output URI, plus a +few options to control how transcoding should be tested. + +## Options + +* `--set-scenario`: Let you set a scenario, it can be a full path to a scenario file or + the name of the scenario (name of the file without the `.scenario` + extension). +* `-l`, `--list-scenarios`: List the avalaible scenarios that can be run. +* `--scenarios-defs-output-file`: The output file to store scenarios details. Implies + `--list-scenario`. +* `-t`, `--inspect-action-type`: Inspect the avalaible action types with which to write scenarios if + no parameter passed, it will list all avalaible action types + otherwize will print the full description of the wanted types. +* `--set-configs`: Let you set a config scenario. The scenario needs to be set as + `config`. You can specify a list of scenarios separated by `:`. It + will override the GST\_VALIDATE\_SCENARIO environment variable. +* `-e`, `--eos-on-shutdown`: If an EOS event should be sent to the pipeline if an interrupt is + received, instead of forcing the pipeline to stop. Sending an EOS + will allow the transcoding to finish the files properly before + exiting. +* `-r`, `--force-reencoding`: Whether to try to force reencoding, meaning trying to only remux if + possible, defaults to `TRUE`. diff --git a/docs/gst-validate.md b/docs/gst-validate.md new file mode 100644 index 0000000000..665fba67e2 --- /dev/null +++ b/docs/gst-validate.md @@ -0,0 +1,58 @@ +--- +short-description: Tool to test GStreamer components +... + +# gst-validate + +`gst-validate` is the simplest `gst-launch`-like pipeline launcher +running inside GstValidate monitoring infrastructure. Monitors are added +to it to identify issues in the used elements. At the end it will print +a report with some information about all the issues encountered during +its run. To view issues as they are detected, set the environment +variable `GST_DEBUG=validate:2`{.shell} and they will get printed in the +GStreamer debug log. You can basically run any [GstPipeline](GstPipeline) pipeline +using this tool. If you are not familiar with `gst-launch` syntax, +please refer to `gst-launch`'s documentation. + +Simple playback pipeline: + + gst-validate-1.0 playbin uri=file:///path/to/some/media/file + +Transcoding pipeline: + + gst-validate-1.0 filesrc location=/media/file/location ! qtdemux name=d ! queue \ + ! x264enc ! h264parse ! mpegtsmux name=m ! progressreport \ + ! filesink location=/root/test.ts d. ! queue ! faac ! m. + +It will list each issue that has been encountered during the execution +of the specified pipeline in a human readable report like: + + issue : buffer is out of the segment range Detected on theoradec0.srcpad at 0:00:00.096556426 + + Details : buffer is out of segment and shouldn't be pushed. Timestamp: 0:00:25.000 - duration: 0:00:00.040 Range: 0:00:00.000 - 0:00:04.520 + Description : buffer being pushed is out of the current segment's start-stop range. Meaning it is going to be discarded downstream without any use + +The return code of the process will be 18 in case a `CRITICAL` issue has +been found. + +# Invocation + +`gst-validate` takes a mandatory description of the pipeline to launch, +similar to `gst-launch`, and some extra options. + +## Options + +* `--set-scenario`: Let you set a scenario, it can be a full path to a scenario file or + the name of the scenario (name of the file without the `.scenario` + extension). +* `-l`, `--list-scenarios`: List the avalaible scenarios that can be run. +* `--scenarios-defs-output-file`: The output file to store scenarios details. Implies + `--list-scenario`. +* `-t`, `--inspect-action-type`: Inspect the avalaible action types with which to write scenarios if + no parameter passed, it will list all avalaible action types + otherwize will print the full description of the wanted types. +* `--set-media-info`: Set a media\_info XML file descriptor to share information about the + media file that will be reproduced. +* `--set-configs`: Let you set a config scenario. The scenario needs to be set as + `config`. You can specify a list of scenarios separated by "`:`". It + will override the GST\_VALIDATE\_SCENARIO environment variable. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000000..f9cfa7ce62 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,26 @@ +# GStreamer Validate + +GstValidate is a tool that allows GStreamer developers to check that the +GstElements they write behave the way they are supposed to. It was first +started to provide plug-ins developers with a tool to check that they +use the framework the proper way. + +GstValidate implements a monitoring logic that allows the system to +check that the elements of a GstPipeline respect some rules GStreamer +components have to follow to make them properly interact together. For +example, a GstValidatePadMonitor will make sure that if we receive a +GstSegment from upstream, an equivalent segment is sent downstream +before any buffer gets out. + +Then GstValidate implements a reporting system that allows users to get +detailed informations about what was not properly handled by the +elements. The generated reports are ordered by level of importance from +"issue" to "critical". + +Some tools have been implemented to help developers validate and test +their GstElement, see [gst-validate](gst-validate.md) for example. + +On top of that, the notion of a [validation scenario](gst-validate-scenarios.md) +has been implemented so that developers can easily execute a set of actions on +pipelines to test real world interactive cases and reproduce existing +issues in a convenient way. diff --git a/docs/meson.build b/docs/meson.build new file mode 100644 index 0000000000..75d60297c9 --- /dev/null +++ b/docs/meson.build @@ -0,0 +1,71 @@ +build_hotdoc = false + +if meson.is_cross_build() + if get_option('doc').enabled() + error('Documentation enabled but building the doc while cross building is not supported yet.') + endif + + message('Documentation not built as building it while cross building is not supported yet.') + subdir_done() +endif + +hotdoc_p = find_program('hotdoc', required: get_option('doc')) +if not hotdoc_p.found() + message('Hotdoc not found, not building the documentation') + subdir_done() +endif + +required_hotdoc_extensions = ['gi-extension'] +if not build_gir + if get_option('doc').enabled() + error('Documentation enabled but introspection not built.') + endif + + message('Introspection not built, can\'t build the documentation') + subdir_done() +endif + +hotdoc = import('hotdoc') +foreach extension: required_hotdoc_extensions + if not hotdoc.has_extensions(extension) + if get_option('doc').enabled() + error('Documentation enabled but @0@ missing'.format(extension)) + endif + + message('@0@ extension not found, not building documentation'.format(extension)) + subdir_done() + endif +endforeach + +excludes = ['gettext.h', + 'gst-validate-internal.h', + 'gst-validate-i18n-lib.c' +] + +build_hotdoc = true +validate_excludes = [] +foreach f: excludes + validate_excludes += [join_paths(meson.current_source_dir(), '..', + 'validate', 'gst', 'validate', f)] +endforeach + +validate_sources = [] +foreach f: gstvalidate_headers + gstvalidate_sources + validate_sources += [join_paths(meson.current_source_dir(), '..', + 'validate', 'gst', 'validate', f)] +endforeach + +hotdoc = import('hotdoc') +plugins_doc = [] +libs_doc = [hotdoc.generate_doc('gst-devtools', + project_version: apiversion, + sitemap: 'sitemap.txt', + index: 'index.md', + gi_c_sources: validate_sources, + gi_c_source_filters: validate_excludes, + gi_index: 'gi-index.md', + gi_smart_index: true, + gi_sources: [validate_gir[0].full_path()], + disable_incremental_build: true, + dependencies : [validate_dep], +)] diff --git a/docs/plugins/index.md b/docs/plugins/index.md new file mode 100644 index 0000000000..1768f822b7 --- /dev/null +++ b/docs/plugins/index.md @@ -0,0 +1,3 @@ +# GstValidate plugins + +GstValidate offers a plugin system to extend the checks and add new functionnality \ No newline at end of file diff --git a/docs/plugins/ssim.md b/docs/plugins/ssim.md new file mode 100644 index 0000000000..afac334dcb --- /dev/null +++ b/docs/plugins/ssim.md @@ -0,0 +1,87 @@ +--- +title: SSIM plugin +short_description: GstValidate plugin to detect frame corruptions +... + +# SSIM plugin + +GstValidate plugin to run the ssim algorithm on the buffers flowing in the +pipeline to find regressions and detect frame corruptions. +It allows you to generate image files from the buffers flowing in the pipeline +(either as raw in the many formats supported by GStreamer or as png) and then +check them against pre generated, reference images. + +The ssim algorithm will set a value of 1.0 when images are perfectly identical, +and -1.0 if they have nothing in common. By default we consider images as similar +if they have at least a ssim value of 0.95 but you can override it defining the value +under which the test will be considered as failed. + +Errors are reported on the GstValidate reporting system. You can also ask +the plugin to generate grey scale output images. Those will be named in a way +that should lets you precisely see where and how the test failed. + +# Configuration + +The configuration of the plugin is done through a validate configuration file, +specified with the %GST_VALIDATE_CONFIG environment variable. Each line starting +with 'ssim,' will configure the ssim plugin. In practice each configuration statement +will lead to the creation of a #GstValidateOverride object which will then dump +image files and if wanted compare those with a set of reference images. + +The following parameters can be passed in the configuration file: + - element-classification: The target element classification as define in + gst_element_class_set_metadata + - output-dir: The directory in which the image files will be saved + - min-avg-priority: (default 0.95): The minimum average similarity + under which we consider the test as failing + - min-lowest-priority: (default 1): The minimum 'lowest' similarity + under which we consider the test as failing + - reference-images-dir: Define the directory in which the files to be + compared can be found + - result-output-dir: The folder in which to store resulting grey scale + images when the test failed. In that folder you will find images + with the structural difference between the expected result and the actual + result. + - output-video-format: The format in which you want the images to be saved + - reference-video-format: The format in which the reference images are stored + - check-recurrence: The recurrence in seconds (as float) the frames should + be dumped and checked.By default it is GST_CLOCK_TIME_NONE, meaning each + and every frame is checked. Not that in any case, after a discontinuity + in the stream (after a seek or a change in the video format for example) + a check is done. And if recurrence == 0, images will be checked only after + such discontinuity + - is-config: Property letting the plugin know that the config line is exclusively + used to configure the following configuration expressions. In practice this + means that it will change the default values for the other configuration + expressions. + - framerate: (GstFraction): The framerate to use to compute frame number from + timestamp, allowing to compare frames by 'frame number' instead of trying to + match timestamp between reference images and output images. + +# Example # + +Let's take a special configuration where we want to compare frames that are +outputted by a video decoder with the ones after a agingtv element we would +call my_agingtv. We force to check one frame every 5.0 seconds only (with +check-recurrence=5.0) so the test is fast. + +The configuration file: + +``` shell + core, action=set-property, target-element-klass=Sink, property-name=sync, property-value=false + + validatessim, is-config=true, output-video-format="I420", reference-video-format="I420" + validatessim, element-classification="Video/Decoder", output-dir=/tmp/test/before-agingtv/ + validatessim, element-name=my_agingtv, output-dir=/tmp/test/after-agingtv/, \ + reference-images-dir=/tmp/test/before-agingtv/, \ + result-output-dir=/tmp/test/failures, check-recurrence=5.0 +``` + +Save that content in a file called check_agingtv_ssim.config + + +## Launch the pipeline + +``` shell + GST_VALIDATE_CONFIG=check_agingtv_ssim.config gst-validate-1.0-debug uridecodebin uri=file://a/file ! videoconvert ! agingtv name=my_agingtv ! videoconvert ! autovideosink +``` diff --git a/docs/plugins/validateflow.md b/docs/plugins/validateflow.md new file mode 100644 index 0000000000..4112028cbe --- /dev/null +++ b/docs/plugins/validateflow.md @@ -0,0 +1,3 @@ +--- +redirect: gst-validate-flow.md +... diff --git a/docs/sitemap.txt b/docs/sitemap.txt new file mode 100644 index 0000000000..091f2ae40c --- /dev/null +++ b/docs/sitemap.txt @@ -0,0 +1,16 @@ +index.md + gst-validate.md + gst-validate-transcoding.md + gst-validate-media-check.md + gst-validate-launcher.md + gst-validate-scenarios.md + gst-validate-test-file.md + gst-validate-config.md + gst-validate-environment-variables.md + gst-validate-action-types.md + ges-validate-action-types.md + gst-validate-flow.md + gi-index + plugins/index.md + plugins/ssim.md + plugins/validateflow.md diff --git a/gst-devtools.doap b/gst-devtools.doap new file mode 100644 index 0000000000..03ad7bf908 --- /dev/null +++ b/gst-devtools.doap @@ -0,0 +1,384 @@ + + + GStreamer development and validation tools + gstreamer + + 1999-10-31 + + GStreamer development and validation tools including GstValidate, a testing + framework aiming at providing GStreamer developers tools that check the + GstElements they write behave the way they are supposed to. + + + GstValidate is a tool that allows GStreamer developers to check that the + GstElements they write behave the way they are supposed to. It was first + started to provide plug-ins developers with a tool to check that they use the + framework the proper way. + + GstValidate implements a monitoring logic that allows the system to check that + the elements of a GstPipeline respect some rules GStreamer components have to + follow so that elements can properly interact together. For example, a + GstValidatePadMonitor will make sure that if we receive a GstSegment from + upstream, an equivalent segment is sent downstream before any buffer gets out. + + Then GstValidate implements a reporting system that allows users to get + detailed informations about what was not properly handle in elements. The + reports are order by level of importance from "issue" to "critical". + + Some tools have been implemented to help the developer validate and test their + GstElement, you can have a look at the command line tools section to find more + information + + On top of those tools, the notion of scenario has been implemented so that + developers can easily execute a set of actions on pipelines and thus test real + world interactive cases and reproduce existing issues in a convenient way. + + + + + + C + + + + + + + + + + + + + 1.19.2 + master + + 2021-09-23 + + + + + + + 1.19.1 + master + + 2021-06-01 + + + + + + + 1.18.0 + master + + 2020-09-08 + + + + + + + 1.17.90 + master + + 2020-08-20 + + + + + + + 1.17.2 + master + + 2020-07-03 + + + + + + + 1.17.1 + master + + 2020-06-19 + + + + + + + 1.16.0 + master + + 2019-04-19 + + + + + + + 1.15.90 + master + + 2019-04-11 + + + + + + + 1.15.2 + master + + 2019-02-26 + + + + + + + 1.15.1 + master + + 2019-01-17 + + + + + + + 1.14.0 + master + + 2018-03-19 + + + + + + + 1.13.91 + master + + 2018-03-13 + + + + + + + 1.13.90 + master + + 2018-03-03 + + + + + + + 1.13.1 + master + + 2018-02-15 + + + + + + + 1.12.4 + 1.12 + + 2017-12-07 + + + + + + + 1.12.3 + 1.12 + + 2017-09-18 + + + + + + + 1.12.2 + 1.12 + + 2017-07-14 + + + + + + + 1.12.1 + 1.12 + + 2017-06-20 + + + + + + + 1.12.0 + master + + 2017-05-04 + + + + + + + 1.11.91 + master + + 2017-04-27 + + + + + + + 1.11.90 + master + + 2017-04-07 + + + + + + + 1.11.2 + master + + 2017-02-24 + + + + + + + 1.11.1 + master + + 2017-01-12 + + + + + + 1.10.0 + master + 2016-11-01 + + + + + + 1.9.90 + master + 2016-09-30 + + + + + + 1.9.2 + master + 2016-09-01 + + + + + + 1.9.1 + master + 2016-06-06 + + + + + + 1.8.0 + master + 2016-03-24 + + + + + + 1.7.91 + master + 2016-03-15 + + + + + + 1.7.90 + master + 2016-03-01 + + + + + + 1.6.0 + 1.6 + 2015-09-25 + + + + + + 1.5.90 + 1.5 + 2015-08-20 + + + + + + + 1.5.2 + 1.5 + + 2014-09-29 + + + + + + + 1.4.0 + 1.4 + + 2014-09-29 + + + + + + + Thibault Saunier + + + + diff --git a/hooks/multi-pre-commit.hook b/hooks/multi-pre-commit.hook new file mode 100755 index 0000000000..98ae380932 --- /dev/null +++ b/hooks/multi-pre-commit.hook @@ -0,0 +1,43 @@ +#!/bin/sh +# Git pre-commit hook that runs multiple hooks specified in $HOOKS. +# Make sure this script is executable. Bypass hooks with git commit --no-verify. + +# This file is inspired by a set of unofficial pre-commit hooks available +# at github. +# Link: https://github.com/githubbrowser/Pre-commit-hooks +# Contact: David Martin, david.martin.mailbox@googlemail.com + + +########################################################### +# SETTINGS: +# pre-commit hooks to be executed. They should be in the same .git/hooks/ folder +# as this script. Hooks should return 0 if successful and nonzero to cancel the +# commit. They are executed in the order in which they are listed. +########################################################### + +HOOKS="hooks/pre-commit.hook hooks/pre-commit-python.hook" + +# exit on error +set -e + +echo $PWD + +for hook in $HOOKS +do + echo "Running hook: $hook" + # run hook if it exists + # if it returns with nonzero exit with 1 and thus abort the commit + if [ -f "$PWD/$hook" ]; then + "$PWD/$hook" + if [ $? != 0 ]; then + exit 1 + fi + else + echo "Error: file $hook not found." + echo "Aborting commit. Make sure the hook is at $PWD/$hook and executable." + echo "You can disable it by removing it from the list" + echo "You can skip all pre-commit hooks with --no-verify (not recommended)." + exit 1 + fi +done + diff --git a/hooks/pre-commit-python.hook b/hooks/pre-commit-python.hook new file mode 100755 index 0000000000..14fbc63bfd --- /dev/null +++ b/hooks/pre-commit-python.hook @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +import os +import subprocess +import sys +import tempfile + +NOT_PYCODESTYLE_COMPLIANT_MESSAGE_PRE = \ + "Your code is not fully pycodestyle compliant and contains"\ + " the following coding style issues:\n\n" + +NOT_PYCODESTYLE_COMPLIANT_MESSAGE_POST = \ + "Please fix these errors and commit again, you can do so "\ + "from the root directory automatically like this, assuming the whole "\ + "file is to be commited:" + +NO_PYCODESTYLE_MESSAGE = \ + "You should install the pycodestyle style checker to be able"\ + " to commit in this repo.\nIt allows us to garantee that "\ + "anything that is commited respects the pycodestyle coding style "\ + "standard.\nYou can install it:\n"\ + " * on ubuntu, debian: $sudo apt-get install pycodestyle \n"\ + " * on fedora: #yum install python3-pycodestyle \n"\ + " * on arch: #pacman -S python-pycodestyle \n"\ + " * or `pip install --user pycodestyle`" + + +def system(*args, **kwargs): + kwargs.setdefault('stdout', subprocess.PIPE) + proc = subprocess.Popen(args, **kwargs) + out, err = proc.communicate() + if isinstance(out, bytes): + out = out.decode() + return out + + +def copy_files_to_tmp_dir(files): + tempdir = tempfile.mkdtemp() + for name in files: + filename = os.path.join(tempdir, name) + filepath = os.path.dirname(filename) + if not os.path.exists(filepath): + os.makedirs(filepath) + with open(filename, 'w') as f: + system('git', 'show', ':' + name, stdout=f) + + return tempdir + + +def main(): + modified_files = system('git', 'diff-index', '--cached', + '--name-only', 'HEAD', '--diff-filter=ACMR').split("\n")[:-1] + non_compliant_files = [] + output_message = None + + for modified_file in modified_files: + try: + if not modified_file.endswith(".py"): + continue + pycodestyle_errors = system('pycodestyle', '--repeat', '--ignore', 'E402,E501,E128,W605,W503', modified_file) + if pycodestyle_errors: + if output_message is None: + output_message = NOT_PYCODESTYLE_COMPLIANT_MESSAGE_PRE + output_message += pycodestyle_errors + non_compliant_files.append(modified_file) + except OSError as e: + output_message = NO_PYCODESTYLE_MESSAGE + break + + if output_message: + print(output_message) + if non_compliant_files: + print(NOT_PYCODESTYLE_COMPLIANT_MESSAGE_POST) + for non_compliant_file in non_compliant_files: + print("autopep8 -i ", non_compliant_file, "; git add ", + non_compliant_file) + print("git commit") + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/hooks/pre-commit.hook b/hooks/pre-commit.hook new file mode 100755 index 0000000000..3c1062b9e0 --- /dev/null +++ b/hooks/pre-commit.hook @@ -0,0 +1,83 @@ +#!/bin/sh +# +# Check that the code follows a consistant code style +# + +# Check for existence of indent, and error out if not present. +# On some *bsd systems the binary seems to be called gnunindent, +# so check for that first. + +version=`gnuindent --version 2>/dev/null` +if test "x$version" = "x"; then + version=`gindent --version 2>/dev/null` + if test "x$version" = "x"; then + version=`indent --version 2>/dev/null` + if test "x$version" = "x"; then + echo "GStreamer git pre-commit hook:" + echo "Did not find GNU indent, please install it before continuing." + exit 1 + else + INDENT=indent + fi + else + INDENT=gindent + fi +else + INDENT=gnuindent +fi + +case `$INDENT --version` in + GNU*) + ;; + default) + echo "GStreamer git pre-commit hook:" + echo "Did not find GNU indent, please install it before continuing." + echo "(Found $INDENT, but it doesn't seem to be GNU indent)" + exit 1 + ;; +esac + +INDENT_PARAMETERS="--braces-on-if-line \ + --case-brace-indentation0 \ + --case-indentation2 \ + --braces-after-struct-decl-line \ + --line-length80 \ + --no-tabs \ + --cuddle-else \ + --dont-line-up-parentheses \ + --continuation-indentation4 \ + --honour-newlines \ + --tab-size8 \ + --indent-level2 \ + --leave-preprocessor-space" + +echo "--Checking style--" +for file in `git diff-index --cached --name-only HEAD --diff-filter=ACMR| grep "\.c$"` ; do + # nf is the temporary checkout. This makes sure we check against the + # revision in the index (and not the checked out version). + nf=`git checkout-index --temp ${file} | cut -f 1` + newfile=`mktemp /tmp/${nf}.XXXXXX` || exit 1 + $INDENT ${INDENT_PARAMETERS} \ + $nf -o $newfile 2>> /dev/null + # FIXME: Call indent twice as it tends to do line-breaks + # different for every second call. + $INDENT ${INDENT_PARAMETERS} \ + $newfile 2>> /dev/null + diff -u -p "${nf}" "${newfile}" + r=$? + rm "${newfile}" + rm "${nf}" + if [ $r != 0 ] ; then +echo "=================================================================================================" +echo " Code style error in: $file " +echo " " +echo " Please fix before committing. Don't forget to run git add before trying to commit again. " +echo " If the whole file is to be committed, this should work (run from the top-level directory): " +echo " " +echo " gst-indent $file; git add $file; git commit" +echo " " +echo "=================================================================================================" + exit 1 + fi +done +echo "--Checking style pass--" diff --git a/meson.build b/meson.build new file mode 100644 index 0000000000..7a606476f1 --- /dev/null +++ b/meson.build @@ -0,0 +1,167 @@ +project('gst-devtools', 'c', + version : '1.19.2', + meson_version : '>= 0.54', + default_options : [ 'warning_level=1', + 'c_std=gnu99', + 'buildtype=debugoptimized' ]) + +gst_version = meson.project_version() +version_arr = gst_version.split('.') +gst_version_major = version_arr[0].to_int() +gst_version_minor = version_arr[1].to_int() +gst_version_micro = version_arr[2].to_int() +if gst_version_minor.is_even() + TESTSUITE_VERSION = '@0@.@1@'.format(gst_version_major, gst_version_minor) +else + TESTSUITE_VERSION = 'master' +endif + +apiversion = '1.0' +soversion = 0 +# maintaining compatibility with the previous libtool versioning +# current = minor * 100 + micro +curversion = gst_version_minor * 100 + gst_version_micro +libversion = '@0@.@1@.0'.format(soversion, curversion) +osxversion = curversion + 1 + +prefix = get_option('prefix') + +glib_req = '>= 2.56.0' +gst_req = '>= @0@.@1@.0'.format(gst_version_major, gst_version_minor) + +cc = meson.get_compiler('c') + +if cc.get_id() == 'msvc' + msvc_args = [ + # Ignore several spurious warnings for things gstreamer does very commonly + # If a warning is completely useless and spammy, use '/wdXXXX' to suppress it + # If a warning is harmless but hard to fix, use '/woXXXX' so it's shown once + # NOTE: Only add warnings here if you are sure they're spurious + '/wd4018', # implicit signed/unsigned conversion + '/wd4146', # unary minus on unsigned (beware INT_MIN) + '/wd4244', # lossy type conversion (e.g. double -> int) + '/wd4305', # truncating type conversion (e.g. double -> float) + cc.get_supported_arguments(['/utf-8']), # set the input encoding to utf-8 + + # Enable some warnings on MSVC to match GCC/Clang behaviour + '/w14062', # enumerator 'identifier' in switch of enum 'enumeration' is not handled + '/w14101', # 'identifier' : unreferenced local variable + '/w14189', # 'identifier' : local variable is initialized but not referenced + ] + add_project_arguments(msvc_args, language: 'c') + # Disable SAFESEH with MSVC for plugins and libs that use external deps that + # are built with MinGW + noseh_link_args = ['/SAFESEH:NO'] +else + noseh_link_args = [] +endif + +# Symbol visibility +if cc.has_argument('-fvisibility=hidden') + add_project_arguments('-fvisibility=hidden', language: 'c') +endif + +# Disable strict aliasing +if cc.has_argument('-fno-strict-aliasing') + add_project_arguments('-fno-strict-aliasing', language: 'c') +endif + +gst_dep = dependency('gstreamer-' + apiversion, version : gst_req, + fallback : ['gstreamer', 'gst_dep']) +gstbase_dep = dependency('gstreamer-base-' + apiversion, version : gst_req, + fallback : ['gstreamer', 'gst_base_dep']) +gst_pbutils_dep = dependency('gstreamer-pbutils-' + apiversion, version : gst_req, + fallback : ['gst-plugins-base', 'pbutils_dep']) +gst_video_dep = dependency('gstreamer-video-' + apiversion, version : gst_req, + fallback : ['gst-plugins-base', 'video_dep']) +gst_controller_dep = dependency('gstreamer-controller-' + apiversion, version : gst_req, + fallback : ['gstreamer', 'gst_controller_dep']) +gst_check_dep = dependency('gstreamer-check-1.0', version : gst_req, + required : get_option('validate'), + fallback : ['gstreamer', 'gst_check_dep']) + +glib_dep = dependency('glib-2.0', version : '>=2.32.0', + fallback: ['glib', 'libglib_dep']) +gmodule_dep = dependency('gmodule-2.0', + fallback: ['glib', 'libgmodule_dep']) +gio_dep = dependency('gio-2.0', + fallback: ['glib', 'libgio_dep']) + +gtk_dep = dependency('gtk+-3.0', required: false) +mathlib = cc.find_library('m', required : false) +dl = cc.find_library('dl', required : false) +json_dep = dependency('json-glib-1.0', + fallback : ['json-glib', 'json_glib_dep']) + +gst_c_args = ['-DHAVE_CONFIG_H', '-DGST_USE_UNSTABLE_API'] + +gir_init_section = [ '--add-init-section=extern void gst_init(gint*,gchar**);' + \ + 'g_setenv("GST_REGISTRY_1.0", "/no/way/this/exists.reg", TRUE);' + \ + 'g_setenv("GST_PLUGIN_PATH_1_0", "", TRUE);' + \ + 'g_setenv("GST_PLUGIN_SYSTEM_PATH_1_0", "", TRUE);' + \ + 'gst_init(NULL,NULL);', '--quiet'] +gir = find_program('g-ir-scanner', required : get_option('introspection')) +build_gir = gir.found() and (not meson.is_cross_build() or get_option('introspection').enabled()) +gnome = import('gnome') + +if gst_dep.type_name() == 'internal' + gst_debug_disabled = not subproject('gstreamer').get_variable('gst_debug') +else + # We can't check that in the case of subprojects as we won't + # be able to build against an internal dependency (which is not built yet) + gst_debug_disabled = cc.has_header_symbol('gst/gstconfig.h', 'GST_DISABLE_GST_DEBUG', dependencies: gst_dep) +endif + +if gst_debug_disabled and cc.has_argument('-Wno-unused') + add_project_arguments('-Wno-unused', language: 'c') +endif + +warning_flags = [ + '-Wmissing-declarations', + '-Wmissing-prototypes', + '-Wredundant-decls', + '-Wundef', + '-Wwrite-strings', + '-Wformat', + '-Wformat-security', + '-Winit-self', + '-Wmissing-include-dirs', + '-Waddress', + '-Wno-multichar', + '-Wdeclaration-after-statement', + '-Wvla', + '-Wpointer-arith', +] + +foreach extra_arg : warning_flags + if cc.has_argument (extra_arg) + add_project_arguments([extra_arg], language: 'c') + endif +endforeach + +pkgconfig = import('pkgconfig') +plugins_install_dir = join_paths(get_option('libdir'), 'gstreamer-1.0') +plugins_pkgconfig_install_dir = join_paths(plugins_install_dir, 'pkgconfig') +if get_option('default_library') == 'shared' + # If we don't build static plugins there is no need to generate pc files + plugins_pkgconfig_install_dir = disabler() +endif +pkgconfig_subdirs = ['gstreamer-1.0'] + +plugins_doc_dep = [] +plugins = [] +i18n = import('i18n') + +python_mod = import('python') +python3 = python_mod.find_installation() + +if not get_option('validate').disabled() + subdir('validate') +endif + +if not get_option('debug_viewer').disabled() + subdir('debug-viewer') +endif +subdir('docs') + +run_command(python3, '-c', 'import shutil; shutil.copy("hooks/multi-pre-commit.hook", ".git/hooks/pre-commit")') diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000000..5da5abf921 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,12 @@ +option('validate', type : 'feature', value : 'auto', + description : 'Build GstValidate') +option('debug_viewer', type : 'feature', value : 'disabled', + description : 'Build GstDebugViewer (GPLv3+)') +option('introspection', type : 'feature', value : 'auto', yield : true, + description : 'Generate gobject-introspection bindings') +option('tests', type : 'feature', value : 'auto', yield : true, + description : 'Build and enable unit tests') +option('nls', type : 'feature', value : 'auto', yield: true, + description : 'Enable native language support (translations)') +option('doc', type : 'feature', value : 'auto', yield: true, + description: 'Enable documentation.') diff --git a/tracer/Makefile b/tracer/Makefile new file mode 100644 index 0000000000..53469d4e58 --- /dev/null +++ b/tracer/Makefile @@ -0,0 +1,12 @@ +TEST_DATA = \ + logs/trace.latency.log + +all: + +logs/trace.latency.log: + mkdir -p logs; \ + GST_DEBUG="GST_TRACER:7" GST_TRACERS=latency GST_DEBUG_FILE=$@ \ + gst-launch-1.0 -q audiotestsrc num-buffers=10 wave=silence ! audioconvert ! autoaudiosink + +check: $(TEST_DATA) + python3 -m unittest discover tracer "*_test.py" diff --git a/tracer/README b/tracer/README new file mode 100644 index 0000000000..606450bc76 --- /dev/null +++ b/tracer/README @@ -0,0 +1,117 @@ +# Add a python api for tracer analyzers + +The python framework will parse the tracer log and aggregate information. +the tool writer will subclass from the Analyzer class and override methods: + + 'handle_tracer_class(self, entry)' + 'handle_tracer_entry(self, entry)' + +Each of those is optional. The entry field is the parsed log line. In most cases +the tools will parse the structure contained in event[Parser.F_MESSAGE]. + +TODO: maybe do apply_tracer_entry() and revert_tracer_entry() - 'apply' will +patch the shared state forward and 'revert' will 'apply' the inverse. This would +let us go back from a state. An application should still take snapshots to allow +for efficient jumping around. If that is the case we could also always go forward +from a snapshot. + +A tool will use an AnalysisRunner to chain one or more analyzers and iterate the +log. A tool can also replay the log multiple times. If it does, it won't work in +'streaming' mode though (streaming mode can offer live stats). + +## TODO +### gst shadow types +Do we want to provide classes like GstBin, GstElement, GstPad, ... to aggregate +info. One way to get them would be to have a GstLogAnalyzer that knows +about data from the log tracer and populates the classes. Tools then can +do e.g. + + pad.name() # pad name + pad.parent().name() # element name + pad.peer().parent() # peer element + pad.parent().state() # element state + +This would allow us to e.g. get a pipeline graph at any point in the log. + +### improve class handling +We already parse the tracer classes. Add helpers that for numeric values that +extract them, and aggregate min/max/avg. Consider other statistical information +(std. deviation) and provide a rolling average for live view. + +## Examples +### Sequence chart generator (mscgen) + +1.) Write file header + +2.) collect element order +Replay the log and use pad_link_pre to collect pad->peer_pad relationship. +Build a sequence of element names and write to msc file. + +3.) collect event processing +Replay the log and use pad_push_event_pre to output message lines to mscfile. + +4.) write footer and run the tool. + +## Latency stats + +1.) collect per sink-latencies and for each sink per source latencies +Calculate min, max, avg. Consider streaming interface, where we update the stats +e.g. once a sec + +2.) in non-streaming mode write final statistic + +## cpu load stats + +Like latency stats, for cpu load. Process cpu load + per thread cpu load. + +## top + +Combine various stats tools into one. + + +# todo +## all tools +* need some (optional) progress reporting + +## structure parser +* add an optional compiled regexp matcher an constructor param +* then we'll parse the whole structure with a single regexp + * this will only parse the top-level structure, we'd then check if there are + nested substructure and handle them + +# Improve tracers +## log +* the log tracer logs args and results into misc categories +* issues + * not easy/reliable to detect its output among other trace output + * not easy to match pre/post lines + * uses own do_log method, instead of gst_tracer_record_log + * if we also log structures, we need to log the 'function' as the + structure-name, also fields would be key=(type)val, instead of key=value + * if we switch to gst_tracer_record_log, we'd need to register 27 formats :/ +## object ids +When logging GstObjects in PTR_FORMAT, we log the name. Unfortunately the name +is not neccesarilly unique over time. Same goes for the object address. +When logging a tracer record we need a way for the scope fileds to uniquely +relate to objects. +a) parse object creation and destruction and build -maps in the tracer + tools: + new-element message: gst_util_seqnum_next() and assoc with name + : get id by name and get data record via id + + if we go this way, the stats tracer would log name in regullar record (which + makes them more readable). + +FIXME: +- if we use stats or log and latency, do we log latency messages twice? + grep -c ":: latency, " logs/trace.all.log + 8365 + + grep ":: event, " logs/trace.all.log | grep -c "name=(string)latency" + 63 + + seems to not happen, regardless of order in GST_TRACERS="latency;stats" + +- why do we log element-ix for buffer, event, ... log-entries in the stats + tracer? We log new-pad, when the pad get added to a parent, so we should know + the element already diff --git a/tracer/gsttr-stats.py b/tracer/gsttr-stats.py new file mode 100644 index 0000000000..192db8683f --- /dev/null +++ b/tracer/gsttr-stats.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python3 +''' +Aggregate values for each tracer event type and print them with some statistics. + +How to run: +1) generate some log +GST_DEBUG="GST_TRACER:7" GST_TRACERS="stats;rusage;latency" GST_DEBUG_FILE=trace.log + +2) print everything +python3 gsttr-stats.py trace.log + +3) print selected entries only +python3 gsttr-stats.py -c latency trace.log +''' +# TODO: +# more options +# - live-update interval (for file=='-') +# +# - for values like timestamps, we only want min/max but no average + +import logging +from fnmatch import fnmatch +from tracer.analysis_runner import AnalysisRunner +from tracer.analyzer import Analyzer +from tracer.parser import Parser +from tracer.structure import Structure + + +logging.basicConfig(level=logging.WARNING) +logger = logging.getLogger('gsttr-stats') + +_SCOPE_RELATED_TO = { + 'GST_TRACER_VALUE_SCOPE_PAD': 'Pad', + 'GST_TRACER_VALUE_SCOPE_ELEMENT': 'Element', + 'GST_TRACER_VALUE_SCOPE_THREAD': 'Thread', + 'GST_TRACER_VALUE_SCOPE_PROCESS': 'Process', +} + +_NUMERIC_TYPES = ('int', 'uint', 'gint', 'guint', 'gint64', 'guint64') + + +class Stats(Analyzer): + + def __init__(self, classes): + super(Stats, self).__init__() + self.classes = classes + self.records = {} + self.data = {} + + def handle_tracer_class(self, event): + s = Structure(event[Parser.F_MESSAGE]) + # TODO only for debugging + # print("tracer class:", repr(s)) + name = s.name[:-len('.class')] + record = { + 'class': s, + 'scope': {}, + 'value': {}, + } + self.records[name] = record + for k, v in s.values.items(): + if v.name == 'scope': + # TODO only for debugging + # print("scope: [%s]=%s" % (k, v)) + record['scope'][k] = v + elif v.name == 'value': + # skip non numeric and those without min/max + if v.values['type'] in _NUMERIC_TYPES and 'min' in v.values and 'max' in v.values: + # TODO only for debugging + # print("value: [%s]=%s" % (k, v)) + record['value'][k] = v + # else: + # TODO only for debugging + # print("skipping value: [%s]=%s" % (k, v)) + + def handle_tracer_entry(self, event): + # use first field in message (structure-id) if none + if event[Parser.F_FUNCTION]: + return + + msg = event[Parser.F_MESSAGE] + p = msg.find(',') + if p == -1: + return + + entry_name = msg[:p] + if self.classes: + if not any([fnmatch(entry_name, c) for c in self.classes]): + return + + record = self.records.get(entry_name) + if not record: + return + + try: + s = Structure(msg) + except ValueError: + logger.warning("failed to parse: '%s'", msg) + return + + # aggregate event based on class + for sk, sv in record['scope'].items(): + # look up bin by scope (or create new) + key = (_SCOPE_RELATED_TO[sv.values['related-to']] + ":" + str(s.values[sk])) + scope = self.data.get(key) + if not scope: + scope = {} + self.data[key] = scope + for vk, vv in record['value'].items(): + # skip optional fields + if vk not in s.values: + continue + if not s.values.get('have-' + vk, True): + continue + + key = entry_name + "/" + vk + data = scope.get(key) + if not data: + data = {'num': 0} + if '_FLAGS_AGGREGATED' not in vv.values.get('flags', ''): + data['sum'] = 0 + if 'max' in vv.values and 'min' in vv.values: + data['min'] = int(vv.values['max']) + data['max'] = int(vv.values['min']) + else: + # aggregated: don't average, collect first value + data['min'] = int(s.values[vk]) + scope[key] = data + # update min/max/sum and count via value + dv = int(s.values[vk]) + data['num'] += 1 + if 'sum' in data: + data['sum'] += dv + if 'min' in data: + data['min'] = min(dv, data['min']) + if 'max' in data: + data['max'] = max(dv, data['max']) + else: + # aggregated: collect last value + data['max'] = dv + + def report(self): + # headline + print("%-45s: %30s: %16s/%16s/%16s" % ( + 'scope', 'value', 'min', 'avg', 'max')) + # iterate scopes + for sk, sv in self.data.items(): + # iterate tracers + for tk, tv in sv.items(): + mi = tv.get('min', '-') + ma = tv.get('max', '-') + if 'sum' in tv: + avg = tv['sum'] / tv['num'] + else: + avg = '-' + if mi == ma: + mi = ma = '-' + if is_time_field(tk): + if mi != '-': + mi = format_ts(mi) + if ma != '-': + ma = format_ts(ma) + if avg != '-': + avg = format_ts(avg) + print("%-45s: %30s: %16s/%16s/%16s" % (sk, tk, mi, avg, ma)) + + +class ListClasses(Analyzer): + + def __init__(self): + super(ListClasses, self).__init__() + + def handle_tracer_class(self, event): + s = Structure(event[Parser.F_MESSAGE]) + print(s.name) + + def handle_tracer_entry(self, event): + raise StopIteration + + +def format_ts(ts): + sec = 1e9 + h = int(ts // (sec * 60 * 60)) + m = int((ts // (sec * 60)) % 60) + s = (ts / sec) + return '{:02d}:{:02d}:{:010.7f}'.format(h, m, s) + + +def is_time_field(f): + # TODO: need proper units + return (f.endswith('/time') or f.endswith('-dts') or f.endswith('-pts') + or f.endswith('-duration')) + + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('file', nargs='?', default='debug.log') + parser.add_argument('-c', '--class', action='append', dest='classes', + help='tracer class selector (default: all)') + parser.add_argument('-l', '--list-classes', action='store_true', + help='show tracer classes') + args = parser.parse_args() + + analyzer = None + if args.list_classes: + analyzer = ListClasses() + else: + analyzer = stats = Stats(args.classes) + + with Parser(args.file) as log: + runner = AnalysisRunner(log) + runner.add_analyzer(analyzer) + runner.run() + + if not args.list_classes: + stats.report() diff --git a/tracer/gsttr-tsplot.py b/tracer/gsttr-tsplot.py new file mode 100644 index 0000000000..15dd380d77 --- /dev/null +++ b/tracer/gsttr-tsplot.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python3 +''' +Plot buffer pts and events in relation to the wall clock. The plots can be used +to spot anomalies, such as processing gaps. + +How to run: +1) generate a log +GST_DEBUG="GST_TRACER:7" GST_TRACERS=stats GST_DEBUG_FILE=trace.log + +2) generate the images +python3 gsttr-tsplot.py trace.log +eog /*.png +''' + +# TODO: +# - improve event plot +# - ideally each event is a vertical line +# http://stackoverflow.com/questions/35105672/vertical-lines-from-data-in-file-in-time-series-plot-using-gnuplot +# - this won't work well if the event is e.g. 'qos' +# - we could sort them by event type and separate them by double new-lines, +# we'd then use 'index ' to plot them in different colors with +# - buffer-pts should be ahead of clock time of the pipeline +# - we don't have the clock ts in the log though + +import logging +import os +from subprocess import Popen, PIPE, DEVNULL +from string import Template +from tracer.analysis_runner import AnalysisRunner +from tracer.analyzer import Analyzer +from tracer.parser import Parser +from tracer.structure import Structure + + +logging.basicConfig(level=logging.WARNING) +logger = logging.getLogger('gsttr-tsplot') + +_HANDLED_CLASSES = ('buffer', 'event', 'new-pad', 'new-element') + +_GST_BUFFER_FLAG_DISCONT = (1 << 6) + +_PLOT_SCRIPT_HEAD = Template( + ''' + set term pngcairo truecolor size $width,$height font "Helvetica,14" + set style line 1 lc rgb '#8b1a0e' pt 1 ps 1 lt 1 lw 1 # --- red + set style line 2 lc rgb '#5e9c36' pt 6 ps 1 lt 1 lw 1 # --- green + set style line 100 lc rgb '#999999' lt 0 lw 1 + set grid back ls 100 + set key font ",10" + set label font ",10" + set tics font ",10" + set xlabel font ",10" + set ylabel font ",10" + ''') +_PLOT_SCRIPT_BODY = Template( + ''' + set output '$png_file_name' + set multiplot layout 3,1 title "$title\\n$subtitle" + + set xlabel "" + set xrange [*:*] writeback + set xtics format "" + set ylabel "Buffer Time (sec.msec)" offset 1,0 + set yrange [*:*] + set ytics + plot '$buf_file_name' using 1:2 with linespoints ls 1 notitle + + set xrange restore + set ylabel "Duration (sec.msec)" offset 1,0 + plot '$buf_file_name' using 1:3 with linespoints ls 1title "cycle", \ + '' using 1:4 with linespoints ls 2 title "duration" + + set xrange restore + set xtics format "%g" scale .5 offset 0,.5 + set xlabel "Clock Time (sec.msec)" offset 0,1 + set ylabel "Events" offset 1,0 + set yrange [$ypos_max:10] + set ytics format "" + plot '$ev_file_name' using 1:4:3:(0) with vectors heads size screen 0.008,90 ls 1 notitle, \ + '' using 2:4 with points ls 1 notitle, \ + '' using 2:4:5 with labels font ',7' offset char 0,-0.5 notitle + unset multiplot + ''') + + +class TsPlot(Analyzer): + '''Generate a timestamp plots from a tracer log. + + These show the buffer pts on the y-axis and the wall-clock time the buffer + was produced on the x-axis. This helps to spot timing issues, such as + stalled elements. + ''' + + def __init__(self, outdir, show_ghost_pads, size): + super(TsPlot, self).__init__() + self.outdir = outdir + self.show_ghost_pads = show_ghost_pads + self.params = { + 'width': size[0], + 'height': size[1], + } + self.buf_files = {} + self.buf_cts = {} + self.ev_files = {} + self.element_names = {} + self.element_info = {} + self.pad_names = {} + self.pad_info = {} + self.ev_labels = {} + self.ev_data = {} + self.ev_ypos = {} + + def _get_data_file(self, files, key, name_template): + data_file = files.get(key) + if not data_file: + pad_name = self.pad_names.get(key) + if pad_name: + file_name = name_template % (self.outdir, key, pad_name) + data_file = open(file_name, 'w') + files[key] = data_file + return data_file + + def _log_event_data(self, pad_file, ix): + data = self.ev_data.get(ix) + if not data: + return + line = self.ev_labels[ix] + ct = data['ct'] + x1 = data['first-ts'] + # TODO: scale 'y' according to max-y of buf or do a multiplot + y = (1 + data['ypos']) * -10 + if ct == 1: + pad_file.write('%f %f %f %f "%s"\n' % (x1, x1, 0.0, y, line)) + else: + x2 = data['last-ts'] + xd = (x2 - x1) + xm = x1 + xd / 2 + pad_file.write('%f %f %f %f "%s (%d)"\n' % (x1, xm, xd, y, line, ct)) + + def _log_event(self, s): + # build a [ts, event-name] data file + ix = int(s.values['pad-ix']) + pad_file = self._get_data_file(self.ev_files, ix, '%s/ev_%d_%s.dat') + if not pad_file: + return + # convert timestamps to seconds + x = int(s.values['ts']) / 1e9 + # some events fire often, labeling each would be unreadable + # so we aggregate a series of events of the same type + line = s.values['name'] + if line == self.ev_labels.get(ix): + # count lines and track last ts + data = self.ev_data[ix] + data['ct'] += 1 + data['last-ts'] = x + else: + self._log_event_data(pad_file, ix) + # start new data, assign a -y coord by event type + if ix not in self.ev_ypos: + ypos = {} + self.ev_ypos[ix] = ypos + else: + ypos = self.ev_ypos[ix] + if line in ypos: + y = ypos[line] + else: + y = len(ypos) + ypos[line] = y + self.ev_labels[ix] = line + self.ev_data[ix] = { + 'ct': 1, + 'first-ts': x, + 'ypos': y, + } + + def _log_buffer(self, s): + if not int(s.values['have-buffer-pts']): + return + # build a [ts, buffer-pts] data file + ix = int(s.values['pad-ix']) + pad_file = self._get_data_file(self.buf_files, ix, '%s/buf_%d_%s.dat') + if not pad_file: + return + flags = int(s.values['buffer-flags']) + if flags & _GST_BUFFER_FLAG_DISCONT: + pad_file.write('\n') + # convert timestamps to e.g. seconds + cts = int(s.values['ts']) / 1e9 + pts = int(s.values['buffer-pts']) / 1e9 + dur = int(s.values['buffer-duration']) / 1e9 + if ix not in self.buf_cts: + dcts = 0 + else: + dcts = cts - self.buf_cts[ix] + self.buf_cts[ix] = cts + pad_file.write('%f %f %f %f\n' % (cts, pts, dcts, dur)) + + def handle_tracer_entry(self, event): + if event[Parser.F_FUNCTION]: + return + + msg = event[Parser.F_MESSAGE] + p = msg.find(',') + if p == -1: + return + entry_name = msg[:p] + if entry_name not in _HANDLED_CLASSES: + return + + try: + s = Structure(msg) + except ValueError: + logger.warning("failed to parse: '%s'", msg) + return + + if entry_name == 'new-element': + ix = int(s.values['ix']) + self.element_names[ix] = s.values['name'] + self.element_info[ix] = 'Element Type: %s' % s.values['type'] + elif entry_name == 'new-pad': + pad_type = s.values['type'] + if self.show_ghost_pads or pad_type not in ['GstGhostPad', 'GstProxyPad']: + parent_ix = int(s.values['parent-ix']) + parent_name = self.element_names.get(parent_ix, '') + ix = int(s.values['ix']) + self.pad_names[ix] = '%s.%s' % (parent_name, s.values['name']) + self.pad_info[ix] = '(%s, Pad Type: %s)' % ( + self.element_info.get(parent_ix, ''), pad_type) + elif entry_name == 'event': + self._log_event(s) + else: # 'buffer' + self._log_buffer(s) + + def report(self): + for ix, pad_file in self.ev_files.items(): + self._log_event_data(pad_file, ix) + pad_file.close() + + script = _PLOT_SCRIPT_HEAD.substitute(self.params) + for ix, pad_file in self.buf_files.items(): + pad_file.close() + name = self.pad_names[ix] + buf_file_name = '%s/buf_%d_%s.dat' % (self.outdir, ix, name) + ev_file_name = '%s/ev_%d_%s.dat' % (self.outdir, ix, name) + png_file_name = '%s/%d_%s.png' % (self.outdir, ix, name) + sub_title = self.pad_info[ix] + ypos_max = (2 + len(self.ev_ypos[ix])) * -10 + script += _PLOT_SCRIPT_BODY.substitute(self.params, title=name, + subtitle=sub_title, buf_file_name=buf_file_name, + ev_file_name=ev_file_name, png_file_name=png_file_name, + ypos_max=ypos_max) + # plot PNGs + p = Popen(['gnuplot'], stdout=DEVNULL, stdin=PIPE) + p.communicate(input=script.encode('utf-8')) + + # cleanup + for ix, pad_file in self.buf_files.items(): + name = self.pad_names[ix] + buf_file_name = '%s/buf_%d_%s.dat' % (self.outdir, ix, name) + os.unlink(buf_file_name) + for ix, pad_file in self.ev_files.items(): + name = self.pad_names[ix] + ev_file_name = '%s/ev_%d_%s.dat' % (self.outdir, ix, name) + os.unlink(ev_file_name) + + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('file', nargs='?', default='debug.log') + parser.add_argument('outdir', nargs='?', default='tsplot') + parser.add_argument('-g', '--ghost-pads', action='store_true', + help='also plot data for ghost-pads') + parser.add_argument('-s', '--size', action='store', default='1600x600', + help='graph size as WxH') + args = parser.parse_args() + + os.makedirs(args.outdir, exist_ok=True) + size = [int(s) for s in args.size.split('x')] + + with Parser(args.file) as log: + tsplot = TsPlot(args.outdir, args.ghost_pads, size) + runner = AnalysisRunner(log) + runner.add_analyzer(tsplot) + runner.run() + + tsplot.report() diff --git a/tracer/tracer/analysis_runner.py b/tracer/tracer/analysis_runner.py new file mode 100644 index 0000000000..25e4725cce --- /dev/null +++ b/tracer/tracer/analysis_runner.py @@ -0,0 +1,48 @@ +try: + from tracer.parser import Parser +except BaseException: + from parser import Parser + + +class AnalysisRunner(object): + """ + Runs several Analyzers over a log. + + Iterates log using a Parser and dispatches to a set of analyzers. + """ + + def __init__(self, log): + self.log = log + self.analyzers = [] + + def add_analyzer(self, analyzer): + self.analyzers.append(analyzer) + + def handle_tracer_class(self, event): + for analyzer in self.analyzers: + analyzer.handle_tracer_class(event) + + def handle_tracer_entry(self, event): + for analyzer in self.analyzers: + analyzer.handle_tracer_entry(event) + + def is_tracer_class(self, event): + return (event[Parser.F_FILENAME] == 'gsttracerrecord.c' + and event[Parser.F_CATEGORY] == 'GST_TRACER' + and '.class' in event[Parser.F_MESSAGE]) + + def is_tracer_entry(self, event): + return (not event[Parser.F_LINE] and not event[Parser.F_FILENAME]) + + def run(self): + try: + for event in self.log: + # check if it is a tracer.class or tracer event + if self.is_tracer_entry(event): + self.handle_tracer_entry(event) + elif self.is_tracer_class(event): + self.handle_tracer_class(event) + # else: + # print("unhandled:", repr(event)) + except StopIteration: + pass diff --git a/tracer/tracer/analysis_runner_test.py b/tracer/tracer/analysis_runner_test.py new file mode 100644 index 0000000000..a86b7ac7c2 --- /dev/null +++ b/tracer/tracer/analysis_runner_test.py @@ -0,0 +1,26 @@ +import unittest + +from tracer.analysis_runner import AnalysisRunner + +TRACER_CLASS = ( + '0:00:00.036373170', 1788, '0x23bca70', 'TRACE', 'GST_TRACER', + 'gsttracerrecord.c', 110, 'gst_tracer_record_build_format', None, + r'latency.class, src=(structure)"scope\\,\\ type\\=\\(type\\)gchararray\\,\\ related-to\\=\\(GstTracerValueScope\\)GST_TRACER_VALUE_SCOPE_PAD\\;", sink=(structure)"scope\\,\\ type\\=\\(type\\)gchararray\\,\\ related-to\\=\\(GstTracerValueScope\\)GST_TRACER_VALUE_SCOPE_PAD\\;", time=(structure)"value\\,\\ type\\=\\(type\\)guint64\\,\\ description\\=\\(string\\)\\"time\\\\\\ it\\\\\\ took\\\\\\ for\\\\\\ the\\\\\\ buffer\\\\\\ to\\\\\\ go\\\\\\ from\\\\\\ src\\\\\\ to\\\\\\ sink\\\\\\ ns\\"\\,\\ flags\\=\\(GstTracerValueFlags\\)GST_TRACER_VALUE_FLAGS_AGGREGATED\\,\\ min\\=\\(guint64\\)0\\,\\ max\\=\\(guint64\\)18446744073709551615\\;";' +) + +TRACER_ENTRY = ( + '0:00:00.142391137', 1788, '0x7f8a201056d0', 'TRACE', 'GST_TRACER', + '', 0, '', None, + r'latency, src=(string)source_src, sink=(string)pulsesink0_sink, time=(guint64)47091349;' +) + + +class TestAnalysisRunner(unittest.TestCase): + + def test_detect_tracer_class(self): + a = AnalysisRunner(None) + self.assertTrue(a.is_tracer_class(TRACER_CLASS)) + + def test_detect_tracer_entry(self): + a = AnalysisRunner(None) + self.assertTrue(a.is_tracer_entry(TRACER_ENTRY)) diff --git a/tracer/tracer/analyzer.py b/tracer/tracer/analyzer.py new file mode 100644 index 0000000000..24041321de --- /dev/null +++ b/tracer/tracer/analyzer.py @@ -0,0 +1,15 @@ +class Analyzer(object): + """ + Base class for a gst tracer analyzer. + + Will be used in conjunction with a AnalysisRunner. + """ + + def __init__(self): + pass + + def handle_tracer_class(self, event): + pass + + def handle_tracer_entry(self, event): + pass diff --git a/tracer/tracer/parser.py b/tracer/tracer/parser.py new file mode 100644 index 0000000000..9f36113c94 --- /dev/null +++ b/tracer/tracer/parser.py @@ -0,0 +1,82 @@ +import os +import re +import sys + + +def _log_line_regex(): + + # "0:00:00.777913000 " + TIME = r"(\d+:\d\d:\d\d\.\d+)\s+" + # "DEBUG " + # LEVEL = "([A-Z]+)\s+" + LEVEL = "(TRACE)\s+" + # "0x8165430 " + THREAD = r"(0x[0-9a-f]+)\s+" + # "GST_REFCOUNTING ", "flacdec " + CATEGORY = "([A-Za-z0-9_-]+)\s+" + # " 3089 " + PID = r"(\d+)\s*" + FILENAME = r"([^:]*):" + LINE = r"(\d+):" + FUNCTION = r"([A-Za-z0-9_]*):" + # FIXME: When non-g(st)object stuff is logged with *_OBJECT (like + # buffers!), the address is printed *without* <> brackets! + OBJECT = "(?:<([^>]+)>)?" + MESSAGE = "(.+)" + + ANSI = "(?:\x1b\\[[0-9;]*m\\s*)*\\s*" + + return [TIME, ANSI, PID, ANSI, THREAD, ANSI, LEVEL, ANSI, CATEGORY, + FILENAME, LINE, FUNCTION, ANSI, OBJECT, ANSI, MESSAGE] + + +class Parser(object): + """ + Helper to parse a tracer log. + + Implements context manager and iterator. + """ + + # record fields + F_TIME = 0 + F_PID = 1 + F_THREAD = 2 + F_LEVEL = 3 + F_CATEGORY = 4 + F_FILENAME = 5 + F_LINE = 6 + F_FUNCTION = 7 + F_OBJECT = 8 + F_MESSAGE = 9 + + def __init__(self, filename): + self.filename = filename + self.log_regex = re.compile(''.join(_log_line_regex())) + self.file = None + + def __enter__(self): + if self.filename != '-': + self.file = open(self.filename, 'rt') + else: + self.file = sys.stdin + return self + + def __exit__(self, *args): + if self.filename != '-': + self.file.close() + self.file = None + + def __iter__(self): + return self + + def __next__(self): + log_regex = self.log_regex + data = self.file + while True: + line = next(data) + match = log_regex.match(line) + if match: + g = list(match.groups()) + g[Parser.F_PID] = int(g[Parser.F_PID]) + g[Parser.F_LINE] = int(g[Parser.F_LINE]) + return g diff --git a/tracer/tracer/parser_perf.py b/tracer/tracer/parser_perf.py new file mode 100644 index 0000000000..0c87a17f6a --- /dev/null +++ b/tracer/tracer/parser_perf.py @@ -0,0 +1,13 @@ +from analysis_runner import AnalysisRunner +from parser import Parser + + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('file', nargs='?', default='debug.log') + args = parser.parse_args() + + with Parser(args.file) as log: + runner = AnalysisRunner(log) + runner.run() diff --git a/tracer/tracer/parser_test.py b/tracer/tracer/parser_test.py new file mode 100644 index 0000000000..2a576ef8cb --- /dev/null +++ b/tracer/tracer/parser_test.py @@ -0,0 +1,47 @@ +import sys +import unittest + +from tracer.parser import Parser + +TESTFILE = './logs/trace.latency.log' + +TEXT_DATA = ['first line', 'second line'] + +TRACER_LOG_DATA = [ + '0:00:00.079422574 7664 0x238ac70 TRACE GST_TRACER :0:: thread-rusage, thread-id=(guint64)37268592, ts=(guint64)79416000, average-cpuload=(uint)1000, current-cpuload=(uint)1000, time=(guint64)79418045;' +] +TRACER_CLASS_LOG_DATA = [ + '0:00:00.041536066 1788 0x14b2150 TRACE GST_TRACER gsttracerrecord.c:110:gst_tracer_record_build_format: latency.class, src=(structure)"scope\,\ type\=\(type\)gchararray\,\ related-to\=\(GstTracerValueScope\)GST_TRACER_VALUE_SCOPE_PAD\;", sink=(structure)"scope\,\ type\=\(type\)gchararray\,\ related-to\=\(GstTracerValueScope\)GST_TRACER_VALUE_SCOPE_PAD\;", time=(structure)"value\,\ type\=\(type\)guint64\,\ description\=\(string\)\"time\\\ it\\\ took\\\ for\\\ the\\\ buffer\\\ to\\\ go\\\ from\\\ src\\\ to\\\ sink\\\ ns\"\,\ flags\=\(GstTracerValueFlags\)GST_TRACER_VALUE_FLAGS_AGGREGATED\,\ min\=\(guint64\)0\,\ max\=\(guint64\)18446744073709551615\;";' +] + + +class TestParser(unittest.TestCase): + + def test___init__(self): + log = Parser(TESTFILE) + self.assertIsNone(log.file) + + def test___enter___with_file(self): + with Parser(TESTFILE) as log: + self.assertIsNotNone(log.file) + + def test___enter___with_stdin(self): + sys.stdin = iter(TEXT_DATA) + with Parser('-') as log: + self.assertIsNotNone(log.file) + + def test_random_text_reports_none(self): + sys.stdin = iter(TEXT_DATA) + with Parser('-') as log: + with self.assertRaises(StopIteration): + next(log) + + def test_log_file_reports_trace_log(self): + with Parser(TESTFILE) as log: + self.assertIsNotNone(next(log)) + + def test_trace_log_parsed(self): + sys.stdin = iter(TRACER_LOG_DATA) + with Parser('-') as log: + event = next(log) + self.assertEqual(len(event), 10) diff --git a/tracer/tracer/structure.py b/tracer/tracer/structure.py new file mode 100644 index 0000000000..b45e7b29a3 --- /dev/null +++ b/tracer/tracer/structure.py @@ -0,0 +1,99 @@ +import logging +import re + +logger = logging.getLogger('structure') + +UNESCAPE = re.compile(r'(? + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/validate/README b/validate/README new file mode 100644 index 0000000000..d93120b662 --- /dev/null +++ b/validate/README @@ -0,0 +1,42 @@ +== Gst-Validate + +The goal of GstValidate is to be able to detect when elements are not +behaving as expected and report it to the user so he knows how things +are supposed to work inside a GstPipeline. In the end, fixing issues +found by the tool will ensure that all elements behave all together in +the expected way. + +The easiest way of using GstValidate is to use one of its command-line +tools, located at tools/ directory. It is also possible to monitor +GstPipelines from any application by using the LD_PRELOAD gstvalidate +lib. The third way of using it is to write your own application that +links and uses libgstvalidate. + +== BUILDING + +Getting the code: + +Releases are available at , download and extract the tarball. If you +want to use latest git version, do: + +git clone + +After cloning or extracting from a tarball, enter the gst-validate directory: + +cd gst-validate + +Build with: + +meson build --prefix= +ninja -C build +sudo ninja -C build install (only if you want to install it) + +Replace with your desired installation path, you can omit +the --prefix argument if you aren't going to install it or if you want the +default /usr/local. It is possible to use gst-validate CLI tools without +installation. + +== INSTRUCTIONS + +If you are looking for informations on how to use gst-validate -> docs/validate-usage.txt +If you are looking for informations on gst-validate design -> docs/validate-design.txt diff --git a/validate/data/bash-completion/completions/gst-validate-1.0 b/validate/data/bash-completion/completions/gst-validate-1.0 new file mode 100644 index 0000000000..8342b8476b --- /dev/null +++ b/validate/data/bash-completion/completions/gst-validate-1.0 @@ -0,0 +1,142 @@ +# GStreamer +# Copyright (C) 2015 Mathieu Duponchelle +# Copyright (C) 2021 Stéphane Cerveau +# +# bash/zsh completion support for gst-validate +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +_GST_HELPERDIR="${BASH_SOURCE[0]%/*}/../helpers" + +if [[ ! -f $_GST_HELPERDIR/gst ]]; then + _GST_HELPERDIR="$(pkg-config --variable=bashhelpersdir gstreamer-1.0)" +else + _GST_HELPERDIR=`cd "$_GST_HELPERDIR"; pwd` +fi + +# Common definitions +. "$_GST_HELPERDIR"/gst + +_gst_validate_all_arguments () +{ + _gst_all_arguments gst-validate-1.0 +} + +_gst_complete_compatible_elements () +{ + COMPREPLY=( $(compgen -W "$($_GST_HELPER --compatible-with $previous_element)" -- $cur) ) +} + +_gst_complete_all_elements () +{ + COMPREPLY=( $(compgen -W "$($_GST_HELPER -l)" -- $cur) ) +} + +_gst_complete_element_properties () +{ + COMPREPLY=( $(compgen -W "$($_GST_HELPER --element-properties $previous_element)" -- $cur) ) +} + +_gstvalidate___exclude_ () { _gst_mandatory_argument gst-validate-1.0; } + +_gst_validate_main () +{ + local i=1 command function_exists previous_element have_previous_element=0 completion_func + + while [[ $i -ne $COMP_CWORD ]]; + do + local var + var="${COMP_WORDS[i]}" + if [[ "$var" == "-"* ]] + then + command="$var" + fi + i=$(($i+1)) + done + + i=1 + while [[ $i -ne $COMP_CWORD ]]; + do + local var + var="${COMP_WORDS[i]}" + + if [[ "$var" == "-"* ]] + then + i=$(($i+1)) + continue + fi + + $(gst-inspect-1.0 --exists $var) + if [ $? -eq 0 ] + then + previous_element="$var" + have_previous_element=1 + fi + i=$(($i+1)) + done + + if [[ "$command" == "--gst"* ]]; then + completion_func="_${command//-/_}" + else + completion_func="_gstlaunch_${command//-/_}" + fi + + # Seems like bash doesn't like "exclude" in function names + if [[ "$completion_func" == "_gstlaunch___exclude" ]] + then + completion_func="_gstvalidate___exclude_" + fi + + declare -f $completion_func >/dev/null 2>&1 + + function_exists=$? + + if [[ "$cur" == "-"* ]]; then + _gst_validate_all_arguments + elif [ $function_exists -eq 0 ] + then + $completion_func + elif [ $have_previous_element -ne 0 ] && [[ "$prev" == "!" ]] + then + _gst_complete_compatible_elements + elif [ $have_previous_element -ne 0 ] + then + _gst_complete_element_properties + else + _gst_complete_all_elements + fi +} + +_gst_validate_func_wrap () +{ + local cur prev + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + $1 +} + +# Setup completion for certain functions defined above by setting common +# variables and workarounds. +# This is NOT a public function; use at your own risk. +_gst_validate_complete () +{ + local wrapper="__launch_wrap${2}" + eval "$wrapper () { _gst_validate_func_wrap $2 ; }" + complete -o bashdefault -o default -o nospace -F $wrapper $1 2>/dev/null \ + || complete -o default -o nospace -F $wrapper $1 +} + +_gst_validate_complete gst-validate-1.0 _gst_validate_main diff --git a/validate/data/gstvalidate.supp b/validate/data/gstvalidate.supp new file mode 100644 index 0000000000..a3d01bfb04 --- /dev/null +++ b/validate/data/gstvalidate.supp @@ -0,0 +1,241 @@ +### This file contains either validate specific suppressions or bugs that we +### can't easily address because they are lower in the stack. +### All the other suppressions should be added ton gstreamer/tests/check/gstreamer.supp + +### Each set of suppression rules should be prefixed by either: +### - FIXED: if the bug/leak has been fixed upstream but we keep the rule +### because the fix may not be deployed yet (because it's lower in the +### stack and not in gst itself). +### - PENDING: if the bug/leak hasn't been fixed yet. In this case, please +### add an url to the bug report. + +# PENDING: https://bugs.freedesktop.org/show_bug.cgi?id=90073 +{ + Leak in mesa fixed with http://lists.freedesktop.org/archives/mesa-dev/2015-April/082101.html + Memcheck:Leak + fun:malloc + fun:read_packet + fun:_xcb_in_read + fun:_xcb_conn_wait + fun:wait_for_reply + fun:xcb_wait_for_reply + fun:dri3_open + fun:dri3_create_screen + fun:AllocAndFetchScreenConfigs + fun:__glXInitialize + fun:glXQueryVersion +} + +{ + Leak in mesa fixed with http://lists.freedesktop.org/archives/mesa-dev/2015-April/082100.html + Memcheck:Leak + ... + fun:get_render_node_from_id_path_tag + fun:loader_get_user_preferred_fd + fun:dri3_create_screen + fun:AllocAndFetchScreenConfigs + fun:__glXInitialize + fun:glXQueryVersion +} + +# FIXED +{ + Fixed in pixman master + Memcheck:Leak + fun:memalign + fun:allocate_and_init + fun:tls_get_addr_tail +} + +# PENDING: https://bugzilla.gnome.org/show_bug.cgi?id=748417 +{ + Ignore all the invalid memory errors from libvpx + Memcheck:Cond + obj:/usr/lib64/libvpx.so* +} + +{ + Ignore all the invalid memory errors from libvpx + Memcheck:Value8 + obj:/usr/lib64/libvpx.so* +} + +# PENDING: https://bugzilla.gnome.org/show_bug.cgi?id=747110 +{ + https://bugzilla.gnome.org/show_bug.cgi?id=747110 + Memcheck:Addr4 + ... + fun:aac_decode_frame_int + fun:aac_decode_frame +} + +# PENDING: https://bugzilla.gnome.org/show_bug.cgi?id=752989 +{ + https://bugzilla.gnome.org/show_bug.cgi?id=752989 + Memcheck:Value4 + ... + fun:aac_decode_frame_int + fun:aac_decode_frame +} + +# PENDING: https://bugs.freedesktop.org/show_bug.cgi?id=90194 +{ + https://bugs.freedesktop.org/show_bug.cgi?id=90194 + Memcheck:Param + ioctl(generic) + fun:ioctl + fun:drmIoctl + fun:drmPrimeHandleToFD +} + +# PENDING: https://bugzilla.gnome.org/show_bug.cgi?id=749232 +# x264 errors +{ + + Memcheck:Cond + ... + fun:x264_encoder_encode +} + +{ + + Memcheck:Value8 + ... + fun:x264_encoder_encode +} + +{ + + Memcheck:Cond + ... + fun:x264_lookahead_thread +} + +{ + + Memcheck:Value8 + ... + fun:x264_lookahead_thread +} + +{ + + Memcheck:Cond + ... + fun:x264_threadpool_thread +} + +{ + + Memcheck:Value8 + obj:/usr/lib64/libx264.so.* +} + +{ + + Memcheck:Cond + obj:/usr/lib64/libx264.so.* +} + +# PENDING: https://bugzilla.gnome.org/show_bug.cgi?id=749428 +# Theora encoder + +{ + + Memcheck:Value8 + ... + fun:theora_enc_handle_frame +} + +{ + + Memcheck:Cond + ... + fun:theora_enc_handle_frame +} + +{ + + Memcheck:Value8 + fun:oc_enc_tokenize_ac +} + +# FIXED +{ + Fixed with mesa master + Memcheck:Cond + fun:lp_build_blend_factor_unswizzled + ... + fun:gst_glimage_sink_on_draw +} + +# FIXED +{ + Fixed with mesa master + Memcheck:Leak + match-leak-kinds: definite + fun:calloc + ... + fun:_do_convert_draw + fun:_do_convert_one_view +} + +# FIXED +{ + Fixed with mesa master + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + ... + fun:gst_gl_shader_compile +} + +# FIXED +{ + Fixed with mesa master + Memcheck:Leak + match-leak-kinds: definite + fun:calloc + ... + fun:_draw_checker_background + fun:_draw_background + fun:gst_gl_video_mixer_callback +} + +{ + #https://bugs.freedesktop.org/show_bug.cgi?id=8215 + #https://bugs.freedesktop.org/show_bug.cgi?id=8428 + #FcPattern uses 'intptr_t elts_offset' instead of 'FcPatternEltPtr elts', + #which confuses valgrind. + font_config_bug_2 + Memcheck:Leak + fun:*alloc + ... + fun:Fc*Add* +} +{ + #Same root cause as font_config_bug_2. + #The 'leak' here is a copy of rule values, as opposed to new values. + font_config_bug_3 + Memcheck:Leak + fun:*alloc + ... + fun:FcConfigValues +} +{ + #Same root cause as font_config_bug_2. + #The 'leak' is copies of font or pattern values into returned pattern values. + font_config_bug_4 + Memcheck:Leak + fun:*alloc + ... + fun:FcValue* + fun:FcFontRenderPrepare +} +{ + font_config_bug_6 + Memcheck:Leak + fun:*alloc + ... + obj:*/libfontconfig.so.* +} \ No newline at end of file diff --git a/validate/data/meson.build b/validate/data/meson.build new file mode 100644 index 0000000000..d8c406c6a8 --- /dev/null +++ b/validate/data/meson.build @@ -0,0 +1 @@ +subdir('scenarios') diff --git a/validate/data/scenarios/adaptive_video_framerate.scenario b/validate/data/scenarios/adaptive_video_framerate.scenario new file mode 100644 index 0000000000..a3043af083 --- /dev/null +++ b/validate/data/scenarios/adaptive_video_framerate.scenario @@ -0,0 +1,5 @@ +description, duration=15.0 +set-restriction, playback-time=5.0, restriction-caps="video/x-raw,framerate=(fraction)5/1" +set-restriction, playback-time=10.0, restriction-caps="video/x-raw,framerate=(fraction)30/1" +eos, playback-time=15.0 +stop, playback-time=15.0 diff --git a/validate/data/scenarios/adaptive_video_framerate_size.scenario b/validate/data/scenarios/adaptive_video_framerate_size.scenario new file mode 100644 index 0000000000..d5cf5963aa --- /dev/null +++ b/validate/data/scenarios/adaptive_video_framerate_size.scenario @@ -0,0 +1,7 @@ +description, duration=25.0 +set-restriction, playback-time=5.0, restriction-caps="video/x-raw,framerate=(fraction)5/1" +set-restriction, playback-time=10.0, restriction-caps="video/x-raw,height=20,width=20,framerate=(fraction)5/1" +set-restriction, playback-time=15.0, restriction-caps="video/x-raw,height=20,width=20,framerate=(fraction)30/1" +set-restriction, playback-time=20.0, restriction-caps="video/x-raw,height=720,width=1280,framerate=(fraction)30/1" +eos, playback-time=25.0 +stop, playback-time=25.0 diff --git a/validate/data/scenarios/adaptive_video_size.scenario b/validate/data/scenarios/adaptive_video_size.scenario new file mode 100644 index 0000000000..c3b5d2ef98 --- /dev/null +++ b/validate/data/scenarios/adaptive_video_size.scenario @@ -0,0 +1,5 @@ +description, duration=15.0 +set-restriction, playback-time=5.0, restriction-caps="video/x-raw,height=480,width=854" +set-restriction, playback-time=10.0, restriction-caps="video/x-raw,height=720,width=1280" +eos, playback-time=15.0 +stop, playback-time=15.0 diff --git a/validate/data/scenarios/alternate_fast_backward_forward.scenario b/validate/data/scenarios/alternate_fast_backward_forward.scenario new file mode 100644 index 0000000000..59138982e1 --- /dev/null +++ b/validate/data/scenarios/alternate_fast_backward_forward.scenario @@ -0,0 +1,14 @@ +description, duration=55.0, min-media-duration=470.0, seek=true, reverse-playback=true +include,location=includes/default-seek-flags.scenario +seek, name=backward-seek, playback-time=0.0, rate=-1.0, start=0.0, stop=310.0, flags="$(default_flags)" +seek, name=forward-seek, playback-time=305.0, rate=1.0, start=305.0, flags="$(default_flags)" +seek, name=Fast-forward-seek, playback-time=310.0, rate=2.0, start=310.0, flags="$(default_flags)" +seek, name=Fast-backward-seek, playback-time=320.0, rate=-2.0, start=0.0, stop=320.0, flags="$(default_flags)" +seek, name=Fast-forward-seek, playback-time=310.0, rate=4.0, start=310.0, flags="$(default_flags)" +seek, name=Fast-backward-seek, playback-time=330.0, rate=-4.0, start=0.0, stop=330.0, flags="$(default_flags)" +seek, name=Fast-forward-seek, playback-time=310.0, rate=8.0, start=310.0, flags="$(default_flags)" +seek, name=Fast-backward-seek, playback-time=350.0, rate=-8.0, start=0.0, stop=350.0, flags="$(default_flags)" +seek, name=Fast-forward-seek, playback-time=310.0, rate=16.0, start=310.0, flags="$(default_flags)" +seek, name=Fast-backward-seek, playback-time=390.0, rate=-16.0, start=0.0, stop=390.0, flags="$(default_flags)" +seek, name=Fast-forward-seek, playback-time=310.0, rate=32.0, start=310.0, flags="$(default_flags)" +seek, name=Fast-backward-seek, playback-time=470.0, rate=-32.0, start=310.0, stop=470.0, flags="$(default_flags)" diff --git a/validate/data/scenarios/camerabin_signal.scenario b/validate/data/scenarios/camerabin_signal.scenario new file mode 100644 index 0000000000..55c0704a02 --- /dev/null +++ b/validate/data/scenarios/camerabin_signal.scenario @@ -0,0 +1,4 @@ +description, duration=20.0 +emit-signal, target-element-name=camerabin0, signal-name=start-capture, playback-time=10.0 +eos, playback-time=20.0 + diff --git a/validate/data/scenarios/change_state_intensive.scenario b/validate/data/scenarios/change_state_intensive.scenario new file mode 100644 index 0000000000..042d6fbc1b --- /dev/null +++ b/validate/data/scenarios/change_state_intensive.scenario @@ -0,0 +1,8 @@ +description, duration=0, summary="Set state to NULL->PLAYING->NULL 20 times", need-clock-sync=true, min-media-duration=1.0, live_content_compatible=True, handles-states=true, ignore-eos=true + +foreach, i=[0, 40], + actions = { + "set-state, state=playing", + "set-state, state=null", + } +stop; diff --git a/validate/data/scenarios/disable_subtitle_track_while_paused.scenario b/validate/data/scenarios/disable_subtitle_track_while_paused.scenario new file mode 100644 index 0000000000..3c679c501f --- /dev/null +++ b/validate/data/scenarios/disable_subtitle_track_while_paused.scenario @@ -0,0 +1,6 @@ +description, summary="Disable subtitle track while pipeline is PAUSED", min-subtitle-track=2, duration=5.0, handles-states=true, needs_preroll=true +pause; +switch-track, name="Disable subtitle", type=text, disable=true +wait, duration=0.5 +play; +stop, playback-time=2.0 diff --git a/validate/data/scenarios/fast_backward.scenario b/validate/data/scenarios/fast_backward.scenario new file mode 100644 index 0000000000..f16072d506 --- /dev/null +++ b/validate/data/scenarios/fast_backward.scenario @@ -0,0 +1,9 @@ +description, duration=30.0, minfo-media-duration=310.0, seek=true, reverse-playback=true, need-clock-sync=true, min-media-duration=310.0, ignore-eos=true +include,location=includes/default-seek-flags.scenario +seek, name=Fast-backward-seek, playback-time=0.0, rate=-2.0, start=0.0, stop=310.0, flags="$(default_flags)" +seek, name=Fast-backward-seek, playback-time=300.0, rate=-4.0, start=0.0, stop=300.0, flags="$(default_flags)" +seek, name=Fast-backward-seek, playback-time=280.0, rate=-8.0, start=0.0, stop=280.0, flags="$(default_flags)" +seek, name=Fast-backward-seek, playback-time=240.0, rate=-16.0, start=0.0, stop=240.0, flags="$(default_flags)" +seek, name=Fast-backward-seek, playback-time=160.0, rate=-32.0, start=0.0, stop=160.0, flags="$(default_flags)" +wait, message-type=eos +stop \ No newline at end of file diff --git a/validate/data/scenarios/fast_forward.scenario b/validate/data/scenarios/fast_forward.scenario new file mode 100644 index 0000000000..89855a6090 --- /dev/null +++ b/validate/data/scenarios/fast_forward.scenario @@ -0,0 +1,8 @@ +description, duration=25.0, seek=true, need-clock-sync=true, min-media-duration=5.0, ignore-eos=true +include,location=includes/default-seek-flags.scenario +seek, name=Fast-forward-seek, playback-time=0.0, rate=2.0, start=0.0, flags="$(default_flags)" +seek, name=Fast-forward-seek, playback-time="min(10.0, $(duration) * 0.0625)", rate=4.0, start=0.0, flags="$(default_flags)" +seek, name=Fast-forward-seek, playback-time="min(20.0, $(duration) * 0.125)", rate=8.0, start=0.0, flags="$(default_flags)" +seek, name=Fast-forward-seek, playback-time="min(40.0, $(duration) * 0.25)", rate=16.0, start=0.0, flags="$(default_flags)" +seek, name=Fast-forward-seek, playback-time="min(80.0, $(duration) * 0.50)", rate=32.0, start=0.0, flags="$(default_flags)" +stop, playback-time="min($(duration) - 0.3, 160.0)", on-message="eos" diff --git a/validate/data/scenarios/force_key_unit.scenario b/validate/data/scenarios/force_key_unit.scenario new file mode 100644 index 0000000000..2a9c8394de --- /dev/null +++ b/validate/data/scenarios/force_key_unit.scenario @@ -0,0 +1,4 @@ +description, duration=2.0 +video-request-key-unit, playback-time=1.0, direction=upstream, running_time=-1.0, all-header=true, count=1 +eos, playback-time=2.0 +stop, playback-time=2.0 diff --git a/validate/data/scenarios/force_rtsp2.scenario b/validate/data/scenarios/force_rtsp2.scenario new file mode 100644 index 0000000000..0d957b6d24 --- /dev/null +++ b/validate/data/scenarios/force_rtsp2.scenario @@ -0,0 +1 @@ +set-property, target-element-factory-name="rtspsrc", property-name=default-rtsp-version, property-value=(string)"2-0" diff --git a/validate/data/scenarios/full_live_rewind.scenario b/validate/data/scenarios/full_live_rewind.scenario new file mode 100644 index 0000000000..35d2fb400b --- /dev/null +++ b/validate/data/scenarios/full_live_rewind.scenario @@ -0,0 +1,9 @@ +# Rewinds a live source completely +# The goal is to check for proper EOS handling when going back to the +# beginning of the live cache +description, seek=true, live_content_required=true, duration=14.0 +# Wait for 5s (can't use playback-time since we don't know the position(live)) +wait, duration=5.0 +# Seek back all the way as fast as possible (from the end, i.e. 'now') +seek, name=End-seek, rate=-16.0, stop=0.0, stop_type=end, start=0.0, start_type=set, flags=flush+trickmode-key-units+trickmode-no-audio +#eos diff --git a/validate/data/scenarios/includes/default-seek-flags.scenario b/validate/data/scenarios/includes/default-seek-flags.scenario new file mode 100644 index 0000000000..2328eb74a2 --- /dev/null +++ b/validate/data/scenarios/includes/default-seek-flags.scenario @@ -0,0 +1,2 @@ +set-vars,\ + default_flags=accurate+flush diff --git a/validate/data/scenarios/meson.build b/validate/data/scenarios/meson.build new file mode 100644 index 0000000000..8e77ca6258 --- /dev/null +++ b/validate/data/scenarios/meson.build @@ -0,0 +1,39 @@ +_scenarios = ['simple_seeks.scenario', + 'seek_forward.scenario', + 'seek_backward.scenario', + 'seek_forward_backward.scenario', + 'reverse_playback.scenario', + 'fast_forward.scenario', + 'fast_backward.scenario', + 'alternate_fast_backward_forward.scenario', + 'pause_resume.scenario', + 'scrub_forward_seeking.scenario', + 'scrub_backward_seeking.scenario', + 'scrub_forward_seeking_full.scenario', + 'scrub_backward_seeking_full.scenario', + 'adaptive_video_size.scenario', + 'adaptive_video_framerate.scenario', + 'adaptive_video_framerate_size.scenario', + 'force_key_unit.scenario', + 'seek_with_stop.scenario', + 'switch_audio_track_while_paused.scenario', + 'switch_subtitle_track.scenario', + 'switch_subtitle_track_while_paused.scenario', + 'disable_subtitle_track_while_paused.scenario', + 'play_15s.scenario', + 'play_5s.scenario', + 'change_state_intensive.scenario', + 'switch_audio_track.scenario', + 'force_rtsp2.scenario',] + +install_data(sources: _scenarios, + install_dir: get_option('datadir') + '/gstreamer-' + + apiversion + '/validate/scenarios') + +install_subdir('includes', + install_dir: get_option('datadir') + '/gstreamer-' + + apiversion + '/validate/scenarios') + +install_subdir('rtsp_overrides', + install_dir: get_option('datadir') + '/gstreamer-' + + apiversion + '/validate/scenarios') diff --git a/validate/data/scenarios/pause_resume.scenario b/validate/data/scenarios/pause_resume.scenario new file mode 100644 index 0000000000..27bfc10909 --- /dev/null +++ b/validate/data/scenarios/pause_resume.scenario @@ -0,0 +1,6 @@ +description, duration=14.0, min-media-duration=7.0 +pause, name=First-pause, playback-time=1.0, duration=1.0 +pause, name=Second-pause, playback-time=3.0, duration=5.0 +pause, name=Third-pause, playback-time=5.0, duration=1.0 +eos, name=Done-testing, playback-time=7.0 +stop, playback-time=7.0 diff --git a/validate/data/scenarios/play_15s.scenario b/validate/data/scenarios/play_15s.scenario new file mode 100644 index 0000000000..af86cb35f2 --- /dev/null +++ b/validate/data/scenarios/play_15s.scenario @@ -0,0 +1,3 @@ +description, duration=15.0 +eos, playback-time=15.0 +stop, playback-time=15.0 diff --git a/validate/data/scenarios/play_15s_live.scenario b/validate/data/scenarios/play_15s_live.scenario new file mode 100644 index 0000000000..3678d44efc --- /dev/null +++ b/validate/data/scenarios/play_15s_live.scenario @@ -0,0 +1,4 @@ +description, duration=15.0, live_content_required=True +wait, duration=15.0 +stop + diff --git a/validate/data/scenarios/play_5s.scenario b/validate/data/scenarios/play_5s.scenario new file mode 100644 index 0000000000..40186e8f02 --- /dev/null +++ b/validate/data/scenarios/play_5s.scenario @@ -0,0 +1,3 @@ +description, duration=5.0 +eos, playback-time=5.0 +stop, playback-time=5.0 diff --git a/validate/data/scenarios/reverse_playback.scenario b/validate/data/scenarios/reverse_playback.scenario new file mode 100644 index 0000000000..90e02cdcee --- /dev/null +++ b/validate/data/scenarios/reverse_playback.scenario @@ -0,0 +1,3 @@ +description, seek=true, reverse-playback=true +include,location=includes/default-seek-flags.scenario +seek, name=Reverse-seek, playback-time=0.0, rate=-1.0, start="max($(duration) - 15.0, 0.0)", stop="$(duration)", flags="$(default_flags)" diff --git a/validate/data/scenarios/rtsp_overrides/includes/default-seek-flags.scenario b/validate/data/scenarios/rtsp_overrides/includes/default-seek-flags.scenario new file mode 100644 index 0000000000..0da3870bc7 --- /dev/null +++ b/validate/data/scenarios/rtsp_overrides/includes/default-seek-flags.scenario @@ -0,0 +1,2 @@ +set-vars,\ + default_flags=flush diff --git a/validate/data/scenarios/scrub_backward_seeking.scenario b/validate/data/scenarios/scrub_backward_seeking.scenario new file mode 100644 index 0000000000..3a8ff47849 --- /dev/null +++ b/validate/data/scenarios/scrub_backward_seeking.scenario @@ -0,0 +1,8 @@ +description, seek=true, handles-states=true, needs_preroll=true +include,location=includes/default-seek-flags.scenario +pause, playback-time=0.0 +seek, playback-time=0.0, start="$(duration) - 0.5", flags="$(default_flags)" +seek, playback-time=0.0, start=position-0.1, repeat="min(10, ($(duration) - 0.6))/0.1", flags="$(default_flags)" +play, playback-time=0.0 +stop, playback-time=1.0 + diff --git a/validate/data/scenarios/scrub_backward_seeking_full.scenario b/validate/data/scenarios/scrub_backward_seeking_full.scenario new file mode 100644 index 0000000000..7cb1f7abee --- /dev/null +++ b/validate/data/scenarios/scrub_backward_seeking_full.scenario @@ -0,0 +1,8 @@ +description, seek=true, handles-states=true, needs_preroll=true +include,location=includes/default-seek-flags.scenario +pause, playback-time=0.0 +seek, playback-time=0.0, start="$(duration) - 0.5", flags="$(default_flags)" +seek, playback-time=0.0, start=position-0.1, repeat="($(duration) - 0.6)/0.1", flags="$(default_flags)" +play, playback-time=0.0 +stop, playback-time=1.0 + diff --git a/validate/data/scenarios/scrub_forward_seeking.scenario b/validate/data/scenarios/scrub_forward_seeking.scenario new file mode 100644 index 0000000000..814dce4fe6 --- /dev/null +++ b/validate/data/scenarios/scrub_forward_seeking.scenario @@ -0,0 +1,6 @@ +description, seek=true, handles-states=true, needs_preroll=true +include,location=includes/default-seek-flags.scenario +pause, playback-time=0.0 +seek, playback-time=0.0, start=position+0.1, repeat="min(10, ($(duration) - 0.5) / 0.1)", flags="$(default_flags)" +play, playback-time=0.0 +stop, playback-time=1.0 diff --git a/validate/data/scenarios/scrub_forward_seeking_full.scenario b/validate/data/scenarios/scrub_forward_seeking_full.scenario new file mode 100644 index 0000000000..d83c8b1e9e --- /dev/null +++ b/validate/data/scenarios/scrub_forward_seeking_full.scenario @@ -0,0 +1,6 @@ +description, seek=true, handles-states=true, needs_preroll=true +include,location=includes/default-seek-flags.scenario +pause, playback-time=0.0 +seek, playback-time=0.0, start=position+0.1, repeat="($(duration) - 0.5)/0.1", flags="$(default_flags)" +play, playback-time=0.0 +stop, playback-time=1.0 diff --git a/validate/data/scenarios/seek_backward.scenario b/validate/data/scenarios/seek_backward.scenario new file mode 100644 index 0000000000..66c9cd3dd3 --- /dev/null +++ b/validate/data/scenarios/seek_backward.scenario @@ -0,0 +1,6 @@ +description, seek=true, duration=30, need-clock-sync=true, ignore-eos=true +include,location=includes/default-seek-flags.scenario +seek, name=Backward-seek, playback-time="min(5.0, ($(duration) / 4))", rate=1.0, start=0.0, flags="$(default_flags)" +seek, name=Backward-seek, playback-time="min(10.0, 2*($(duration) / 4))", rate=1.0, start="min(5.0, $(duration) / 4)", flags="$(default_flags)" +seek, name=Backward-seek, playback-time="min(15.0, 3*($(duration) / 4))", rate=1.0, start="min(10.0, 2*($(duration) / 4))", flags="$(default_flags)" +stop, playback-time="min(15.0, 3*($(duration) / 4))" diff --git a/validate/data/scenarios/seek_end_live.scenario b/validate/data/scenarios/seek_end_live.scenario new file mode 100644 index 0000000000..f431393fdb --- /dev/null +++ b/validate/data/scenarios/seek_end_live.scenario @@ -0,0 +1,17 @@ +#FIXME : Rename to seek_end_live +description, seek=true, live_content_required=true, duration=14.0 +# Wait for 5s (can't use playback-time since we don't know the position(live)) +wait, duration=5.0 +# Seek back 1min (from the end, i.e. 'now') +seek, name=End-seek, rate=1.0, start=-60.0, start_type=end, flags=flush +wait, duration=5.0 +# go back to live ! +seek, name=End-seek, rate=1.0, start=0.0, start_type=end, flags=flush +wait, duration=5.0 +# Now seek backwards from the end +seek, name=End-seek, rate=-1.0, start=0.0, start_type=none, stop=0.0, stop_type=end, flags=flush +wait, duration=5.0 +# Try a simple seek (without setting the stop) +seek, name=End-seek, rate=1.0, start_type=end, start=0.0, flags=flush +wait, duration=5.0 +stop diff --git a/validate/data/scenarios/seek_forward.scenario b/validate/data/scenarios/seek_forward.scenario new file mode 100644 index 0000000000..9949e4149c --- /dev/null +++ b/validate/data/scenarios/seek_forward.scenario @@ -0,0 +1,6 @@ +description, seek=true, duration=20, need-clock-sync=true, ignore-eos=true +include,location=includes/default-seek-flags.scenario +seek, name=First-forward-seek, playback-time="min(5.0, ($(duration)/8))", start="min(10, 2*($(duration)/8))", flags="$(default_flags)" +seek, name=Second-forward-seek, playback-time="min(15.0, 3*($(duration)/8))", start="min(20, 4*($(duration)/8))", flags="$(default_flags)" +seek, name=Third-forward-seek, playback-time="min(25, 5*($(duration)/8))", start="min(30.0, 6*($(duration)/8))", flags="$(default_flags)" +stop, playback-time="min($(duration) - 1, 35)", on-message=eos \ No newline at end of file diff --git a/validate/data/scenarios/seek_forward_backward.scenario b/validate/data/scenarios/seek_forward_backward.scenario new file mode 100644 index 0000000000..4b669aff75 --- /dev/null +++ b/validate/data/scenarios/seek_forward_backward.scenario @@ -0,0 +1,10 @@ +description, seek=true, duration=40, min-media-duration=45.0 +include,location=includes/default-seek-flags.scenario +seek, name=Forward-seek, playback-time=0.0, rate=1.0, start=5.0, flags="$(default_flags)" +seek, name=Backward-seek, playback-time=10.0, rate=1.0, start=0.0, flags="$(default_flags)" +seek, name=Backward-seek, playback-time=5.0, rate=1.0, start=25.0, stop=-1, flags="$(default_flags)" +seek, name=Backward-seek, playback-time=30.0, rate=1.0, start=0.0, flags="$(default_flags)" +seek, name=Forward-seek, playback-time=5.0, rate=1.0, start=15.0, flags="$(default_flags)" +seek, name=Forward-seek, playback-time=20.0, rate=1.0, start=35.0, flags="$(default_flags)" +seek, name=Backward-seek, playback-time=40.0, rate=1.0, start=25.0, flags="$(default_flags)" +seek, name=Last-backward-seek, playback-time=30.0, rate=1.0, start=5.0, stop=10.0, flags="$(default_flags)" diff --git a/validate/data/scenarios/seek_with_stop.scenario b/validate/data/scenarios/seek_with_stop.scenario new file mode 100644 index 0000000000..b4b7e3f60c --- /dev/null +++ b/validate/data/scenarios/seek_with_stop.scenario @@ -0,0 +1,3 @@ +description, seek=true, duration=5.0, need_clock_sync=true, min-media-duration=2 +include,location=includes/default-seek-flags.scenario +seek, playback-time=1.0, start=0.0, stop="min(5.0, duration-1.0)", flags="$(default_flags)" diff --git a/validate/data/scenarios/simple_seeks.scenario b/validate/data/scenarios/simple_seeks.scenario new file mode 100644 index 0000000000..ca41f6ca2c --- /dev/null +++ b/validate/data/scenarios/simple_seeks.scenario @@ -0,0 +1,5 @@ +description, seek=true, duration=5.0 +include,location=includes/default-seek-flags.scenario +seek, playback-time=1.0, rate=1.0, start=2.0, flags="$(default_flags)" +seek, playback-time=3.0, rate=1.0, start=0.0, flags="$(default_flags)" +seek, playback-time=1.0, rate=1.0, start=2.0, stop=3.0, flags="$(default_flags)" diff --git a/validate/data/scenarios/switch_audio_track.scenario b/validate/data/scenarios/switch_audio_track.scenario new file mode 100644 index 0000000000..b1a968b661 --- /dev/null +++ b/validate/data/scenarios/switch_audio_track.scenario @@ -0,0 +1,3 @@ +description, summary="Change audio track at 5 second to the second audio track", min-audio-track=2, duration=10.0, min-media-duration=5.1 +switch-track, name=Next-audio-track, playback-time=5.0, type=audio, index=(string)+1 +stop, playback-time=10.0 diff --git a/validate/data/scenarios/switch_audio_track_while_paused.scenario b/validate/data/scenarios/switch_audio_track_while_paused.scenario new file mode 100644 index 0000000000..fd4c36249f --- /dev/null +++ b/validate/data/scenarios/switch_audio_track_while_paused.scenario @@ -0,0 +1,11 @@ +description, summary="Change audio track while pipeline is paused", min-audio-track=2, duration=6.0, need-clock-sync=true, needs_preroll=true +pause, playback-time=1.0; + +# Wait so that humans can see the pipeline is paused +wait, duration=0.5 +switch-track, name=Next-audio-track, type=audio, index=(string)+1 + +# Wait so that humans can see the pipeline is paused +wait, duration=0.5 +play; +stop, playback-time=5.0 diff --git a/validate/data/scenarios/switch_set_external_subtitle.scenario b/validate/data/scenarios/switch_set_external_subtitle.scenario new file mode 100644 index 0000000000..7f92217a19 --- /dev/null +++ b/validate/data/scenarios/switch_set_external_subtitle.scenario @@ -0,0 +1 @@ +description, summary="Change subtitle track at 1 second while paused", duration=5.0, needs-ext-file="subtitles/%s.1.srt:subtitles/%s.1.srt" diff --git a/validate/data/scenarios/switch_subtitle_track.scenario b/validate/data/scenarios/switch_subtitle_track.scenario new file mode 100644 index 0000000000..216e7ce91f --- /dev/null +++ b/validate/data/scenarios/switch_subtitle_track.scenario @@ -0,0 +1,3 @@ +description, summary="Change subtitle track at 1 second while playing back", min-subtitle-track=2, duration=5.0, need-clock-sync=true +switch-track, playback-time=1.0, type=text, index=(string)+1 +stop, playback-time=5.0 diff --git a/validate/data/scenarios/switch_subtitle_track_while_paused.scenario b/validate/data/scenarios/switch_subtitle_track_while_paused.scenario new file mode 100644 index 0000000000..6119142569 --- /dev/null +++ b/validate/data/scenarios/switch_subtitle_track_while_paused.scenario @@ -0,0 +1,7 @@ +description, summary="Change subtitle track while pipeline is PAUSED", min-subtitle-track=2, duration=5.0, handles-states=true, need-clock-sync=true, needs_preroll=true +pause; +wait, duration=0.5 +switch-track, type=text, index=(string)+1 +wait, duration=0.5 +play; +stop, playback-time=5.0 diff --git a/validate/data/scenarios/trick_mode_seeks.scenario b/validate/data/scenarios/trick_mode_seeks.scenario new file mode 100644 index 0000000000..d0a82bc26d --- /dev/null +++ b/validate/data/scenarios/trick_mode_seeks.scenario @@ -0,0 +1,9 @@ +description, duration=10.0, seek=true, need-clock-sync=true, min-media-duration=8.0, min-video-track=1 +seek, name=Fast-forward-seek, playback-time="min(5.0, duration*0.0625)", rate=2.0, start=0.0, flags=flush+trickmode-key-units +seek, name=Fast-forward-seek, playback-time="min(10.0, duration*0.0625)", rate=4.0, start=0.0, flags=flush+trickmode-key-units +seek, name=Fast-forward-seek, playback-time="min(20.0, duration*0.125)", rate=8.0, start=0.0, flags=flush+trickmode-key-units +seek, name=Fast-forward-seek, playback-time="min(40.0, duration*0.25)", rate=16.0, start=0.0, flags=flush+trickmode-key-units +seek, name=Fast-forward-seek, playback-time="min(80.0, duration*0.50)", rate=32.0, start=0.0, flags=flush+trickmode-key-units +# and go back to regular playback +seek, name=regular-playback, playback-time="min(160.0, duration*0.75)", rate=1.0, start=0.0, flags=flush +stop, playback-time="min(10.0, duration*0.0625)" diff --git a/validate/data/scenarios/update_start.scenario b/validate/data/scenarios/update_start.scenario new file mode 100644 index 0000000000..846f4db604 --- /dev/null +++ b/validate/data/scenarios/update_start.scenario @@ -0,0 +1,3 @@ +description, summary="Use the set seek type to seek at 5 seconds after 2 seconds", seek=true +include,location=includes/default-seek-flags.scenario +seek, playback-time=2.0, rate=1.0, start_type=set, start=5.0, stop_type=none, stop=0.0, flags="$(default_flags)" diff --git a/validate/data/scenarios/update_stop.scenario b/validate/data/scenarios/update_stop.scenario new file mode 100644 index 0000000000..584e16d6e6 --- /dev/null +++ b/validate/data/scenarios/update_stop.scenario @@ -0,0 +1,4 @@ +description, summary="Use the set seek type to seek at 0 secs stop 10secs after 5 secs", seek=true +description, duration=15.0, seek=true +include,location=includes/default-seek-flags.scenario +seek, playback-time=5.0, rate=1.0, start_type=none, start=0.0, stop_type=set, stop=10.0, flags="$(default_flags)" diff --git a/validate/data/valgrind.config b/validate/data/valgrind.config new file mode 100644 index 0000000000..5bc1aafea9 --- /dev/null +++ b/validate/data/valgrind.config @@ -0,0 +1,2 @@ +core, action=set-property, target-element-klass=Filter, property-name=qos, property-value=false +core, action=set-property, target-element-klass=Sink, property-name=max-lateness, property-value=-1, optional=true diff --git a/validate/docs/release.txt b/validate/docs/release.txt new file mode 100644 index 0000000000..d2d83da72d --- /dev/null +++ b/validate/docs/release.txt @@ -0,0 +1,2 @@ +To be able to use the new-release script from www/bin/ you will need to link $GST/gst-devtools/validate to +$GST/gst-validate/ -- After doing that you can follow the standard GStreamer releasing procedure. diff --git a/validate/docs/validate-design.txt b/validate/docs/validate-design.txt new file mode 100644 index 0000000000..cb1a701636 --- /dev/null +++ b/validate/docs/validate-design.txt @@ -0,0 +1,59 @@ +== Main components + +Gst-validate is composed of 4 parts: the issues, the reports, the runner and +the reporters. + += Issue +Gst-Validate's main target is finding problems in GStreamer elements/pipelines. +To make it easier to track down exactly what happens, the tests run by +Gst-Validate use an extensible list of 'Issues'. Each Issue describes a +potential error situation and has an unique ID and a severity level. + +The issues list can be extended by 3rd party libraries if specific needs +should be met. + += Reporters +A reporter is the object that implements the GstValidateReporter interface and +is responsible for performing tests on some target element/scenario. The +reporter is able to create 'Reports' whenever a test it executes fails. + += Reports +The GstValidateReports are created whenever a test fails, they are posted to the +stderr and also are posted to the GstValidateRunner for accumulation. + +Each report contains information about the object that generated the issue, +the issue associated with the report and a specific debug message for the case, +this helps tracking down the problem and fixing it. + += Runner +The GstValidateRunner is the point of communication for the app to gst-validate +monitoring. It provides an API to gather reports and to make them accessible +to the application. + +== Reporter types + += Monitors +The monitors are used to wrap around pipelines (and elements and pads) and +attach to their data flow handling functions to be able to intercept the data +and compare it with the expected behaviors. There are 3 types of monitors: + + * GstValidateElementMonitor + * GstValidateBinMonitor + * GstValidatePadMonitor + +All 3 inherit from the base GstValidateMonitor class. Their name suggest what +they monitor and they have a relationship to their children and parents. A bin +monitor has, possibly, child element monitors and element monitors have child +pad monitors. The monitors are responsible for listening to new children added +to their monitored object and creating monitors for them. For example, element +monitors listen to element's pad-added signal and create pad monitors whenever +a new pad is added. + +Most (if not all) the checks are implemented at the GstValidatePadMonitor, +as it is where the data flow happens. + += FileChecker +The file checker is another reporter that is used to make sure a file has a +few expected properties. It inspects the file and compares the results with +expected values set by the user. Values such as file duration, file size, if +it can be played back and also if its encoding and container types. diff --git a/validate/docs/validate-usage.txt b/validate/docs/validate-usage.txt new file mode 100644 index 0000000000..44df3d7a1a --- /dev/null +++ b/validate/docs/validate-usage.txt @@ -0,0 +1,90 @@ +=== The GstValidate CLI Tools + +The commands here assume that you have installed gst-validate. If this +is not the case, go into the gst-validate directory and call the tools +directly with the path tools/ + +1- gst-validate-1.0: It is the simplest tool and is used to run a gst +launch style pipeline. Monitors are added to it to identify issues in the +used elements. At the end a report will be printed, this report will +contain information about all issues that were encountered while running +gst-validate. To view issues as they are created, set the environment +variable GST_DEBUG=validate:2 and it will be printed as gstreamer +debugging. You can basically run any GstPipeline pipeline using it. +If you are not familiar with gst-launch syntax, please refer to +gst-launch's documentation. + +Examples: + + # Simple playback pipeline + gst-validate-1.0 playbin uri=file:///path/to/some/media/file + + # Transcoding pipeline + gst-validate-1.0 filesrc location=/root/Videos/big_buck_bunny_1080p_h264.mov ! \ + qtdemux name=d ! queue ! x264enc ! h264parse ! mpegtsmux name=m ! progressreport ! filesink location=/root/test.ts \ + d. ! queue ! faac ! m. + +You can also activate what we call "scenarios" which will execute +actions on the pipeline. Those actions can be for example, "set pipeline +to pause", "seek to N with rate=x" etc, using the following syntax: + + gst-validate-1.0 playbin uri=file:///path/to/some/media/file --set-scenario=seek_forward + +You can list all available scenarios using: + + gst-validate-transcoding-1.0 --list-scenarios + +Scenarios are simple text files describing a list of actions, you can find the +source scenario files in validate/data/ + +You can find more information about scenarios on the GstValidateScenario documentation: http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-validate/html/GstValidateScenario.html + +You can find more information about the various action types available to be executed with: + + gst-validate-1.0 -t + +or: + + gst-validate-transcoding-1.0 -t + + 2- gst-validate-transcoding-1.0: Transcodes input-uri to output-uri, +using the given encoding profile. The pipeline will be monitored for +possible issues detection using the gst-validate lib, at the end of +execution, a report containing information about all found issues will +be printed. + +Example: + + # Will transcode file://path/to/some/media/file to H264/AAC into mp4 + gst-validate-transcoding-1.0 -o 'video/quicktime,variant=iso:video/x-h264:audio/mpeg,mpegversion=4' \ + file://path/to/some/media/file file:///path/to/destination_h264_aac.qt + +The same scenarios can be activated on gst-validate-transcoding-1.0 as +with gst-validate-1.0 + + 3- gst-validate-media-check-1.0: Analyzes a media file and writes the +results to stdout or a file. It can also compare the results found with +another results file for identifying regressions. The monitoring lib +from gst-validate will be enabled during the tests to identify issues +with the GStreamer elements involved with the media file's container and +codec types. It will actually do a series of checks over the media file. + +Example: + + # Will check various media properties from the file + gst-validate-media-check-1.0 file://path/to/some/media/file + +=== LD_PRELOAD / Testing with exiting application + +If you want to test an already existing application without modifying it. Just +use: + +LD_PRELOAD=path/to/libgstvalidatepreload.so yourapp ... + +gst-validate will try to replace GstPipeline creating functions and configure +monitors automatically for you, reports will be printed to stderr when +they are found. You can also use GST_DEBUG to view the issues that were found + +NOTES: The exit code will be "18" in case a critical issue has +been seen while running any of those tools. + diff --git a/validate/gst-libs/gst/meson.build b/validate/gst-libs/gst/meson.build new file mode 100644 index 0000000000..e3b50ad587 --- /dev/null +++ b/validate/gst-libs/gst/meson.build @@ -0,0 +1 @@ +subdir('video') diff --git a/validate/gst-libs/gst/video/gssim.c b/validate/gst-libs/gst/video/gssim.c new file mode 100644 index 0000000000..0e23a2f7ac --- /dev/null +++ b/validate/gst-libs/gst/video/gssim.c @@ -0,0 +1,449 @@ +/* GStreamer + * + * Copyright (C) 2014 Mathieu Duponchelle + * Copyright (C) 2015 Raspberry Pi Foundation + * Author: Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#include +#include +#include +#include + +#include "gssim.h" + +typedef gfloat (*SSimWeightFunc) (Gssim * self, gint y, gint x); + +typedef struct _SSimWindowCache +{ + gint x_window_start; + gint x_weight_start; + gint x_window_end; + gint y_window_start; + gint y_weight_start; + gint y_window_end; + gfloat element_summ; +} SSimWindowCache; + +struct _GssimPrivate +{ + gint width; + gint height; + gint windowsize; + gint windowtype; + SSimWindowCache *windows; + gfloat *weights; + gfloat const1; + gfloat const2; + gfloat sigma; + + gfloat *orgmu; + + GstVideoConverter *converter; + GstVideoInfo in_info, out_info; +}; + +/* *INDENT-OFF* */ +G_DEFINE_TYPE_WITH_PRIVATE (Gssim, gssim, GST_TYPE_OBJECT) +/* *INDENT-ON* */ + +enum +{ + PROP_FIRST_PROP = 1, + N_PROPS +}; + +static void +gssim_calculate_mu (Gssim * self, guint8 * buf) +{ + gint oy, ox, iy, ix; + + for (oy = 0; oy < self->priv->height; oy++) { + for (ox = 0; ox < self->priv->width; ox++) { + gfloat mu = 0; + gfloat elsumm; + gint weight_y_base, weight_x_base; + gint weight_offset; + gint pixel_offset; + gint winstart_y; + gint wghstart_y; + gint winend_y; + gint winstart_x; + gint wghstart_x; + gint winend_x; + gfloat weight; + gint source_offset; + + source_offset = oy * self->priv->width + ox; + + winstart_x = self->priv->windows[source_offset].x_window_start; + wghstart_x = self->priv->windows[source_offset].x_weight_start; + winend_x = self->priv->windows[source_offset].x_window_end; + winstart_y = self->priv->windows[source_offset].y_window_start; + wghstart_y = self->priv->windows[source_offset].y_weight_start; + winend_y = self->priv->windows[source_offset].y_window_end; + elsumm = self->priv->windows[source_offset].element_summ; + + switch (self->priv->windowtype) { + case 0: + for (iy = winstart_y; iy <= winend_y; iy++) { + pixel_offset = iy * self->priv->width; + for (ix = winstart_x; ix <= winend_x; ix++) + mu += buf[pixel_offset + ix]; + } + mu = mu / elsumm; + break; + case 1: + + weight_y_base = wghstart_y - winstart_y; + weight_x_base = wghstart_x - winstart_x; + + for (iy = winstart_y; iy <= winend_y; iy++) { + pixel_offset = iy * self->priv->width; + weight_offset = (weight_y_base + iy) * self->priv->windowsize + + weight_x_base; + for (ix = winstart_x; ix <= winend_x; ix++) { + weight = self->priv->weights[weight_offset + ix]; + mu += weight * buf[pixel_offset + ix]; + } + } + mu = mu / elsumm; + break; + } + self->priv->orgmu[oy * self->priv->width + ox] = mu; + } + } +} + +static gfloat +ssim_weight_func_none (Gssim * self, gint y, gint x) +{ + return 1; +} + +static gfloat +ssim_weight_func_gauss (Gssim * self, gint y, gint x) +{ + gfloat coord = sqrt (x * x + y * y); + return exp (-1 * (coord * coord) / (2 * self->priv->sigma * + self->priv->sigma)) / (self->priv->sigma * sqrt (2 * G_PI)); +} + + +static gboolean +gssim_regenerate_windows (Gssim * self) +{ + gint windowiseven; + gint y, x, y2, x2; + SSimWeightFunc func; + gfloat normal_summ = 0; + gint normal_count = 0; + + g_free (self->priv->weights); + + self->priv->weights = + g_new (gfloat, self->priv->windowsize * self->priv->windowsize); + + windowiseven = + ((gint) self->priv->windowsize / 2) * 2 == self->priv->windowsize ? 1 : 0; + + g_free (self->priv->windows); + + self->priv->windows = + g_new (SSimWindowCache, self->priv->height * self->priv->width); + + switch (self->priv->windowtype) { + case 0: + func = ssim_weight_func_none; + break; + case 1: + func = ssim_weight_func_gauss; + break; + default: + self->priv->windowtype = 1; + func = ssim_weight_func_gauss; + } + + for (y = 0; y < self->priv->windowsize; y++) { + gint yoffset = y * self->priv->windowsize; + for (x = 0; x < self->priv->windowsize; x++) { + self->priv->weights[yoffset + x] = + func (self, x - self->priv->windowsize / 2 + windowiseven, + y - self->priv->windowsize / 2 + windowiseven); + normal_summ += self->priv->weights[yoffset + x]; + normal_count++; + } + } + + for (y = 0; y < self->priv->height; y++) { + for (x = 0; x < self->priv->width; x++) { + SSimWindowCache win; + gint element_count = 0; + + win.x_window_start = x - self->priv->windowsize / 2 + windowiseven; + win.x_weight_start = 0; + if (win.x_window_start < 0) { + win.x_weight_start = -win.x_window_start; + win.x_window_start = 0; + } + + win.x_window_end = x + self->priv->windowsize / 2; + if (win.x_window_end >= self->priv->width) + win.x_window_end = self->priv->width - 1; + + win.y_window_start = y - self->priv->windowsize / 2 + windowiseven; + win.y_weight_start = 0; + if (win.y_window_start < 0) { + win.y_weight_start = -win.y_window_start; + win.y_window_start = 0; + } + + win.y_window_end = y + self->priv->windowsize / 2; + if (win.y_window_end >= self->priv->height) + win.y_window_end = self->priv->height - 1; + + win.element_summ = 0; + element_count = (win.y_window_end - win.y_window_start + 1) * + (win.x_window_end - win.x_window_start + 1); + if (element_count == normal_count) + win.element_summ = normal_summ; + else { + for (y2 = win.y_weight_start; y2 < self->priv->windowsize; y2++) { + for (x2 = win.x_weight_start; x2 < self->priv->windowsize; x2++) { + win.element_summ += + self->priv->weights[y2 * self->priv->windowsize + x2]; + } + } + } + self->priv->windows[(y * self->priv->width + x)] = win; + } + } + + /* FIXME: while 0.01 and 0.03 are pretty much static, the 255 implies that + * we're working with 8-bit-per-color-component format, which may not be true + */ + self->priv->const1 = 0.01 * 255 * 0.01 * 255; + self->priv->const2 = 0.03 * 255 * 0.03 * 255; + return TRUE; +} + +void +gssim_compare (Gssim * self, guint8 * org, guint8 * mod, + guint8 * out, gfloat * mean, gfloat * lowest, gfloat * highest) +{ + gint oy, ox, iy, ix; + gfloat cumulative_ssim = 0; + *lowest = G_MAXFLOAT; + *highest = -G_MAXFLOAT; + + if (self->priv->windows == NULL) + gssim_regenerate_windows (self); + gssim_calculate_mu (self, org); + + for (oy = 0; oy < self->priv->height; oy++) { + for (ox = 0; ox < self->priv->width; ox++) { + gfloat mu_o = 0, mu_m = 0; + gdouble sigma_o = 0, sigma_m = 0, sigma_om = 0; + gfloat tmp1, tmp2; + gfloat elsumm = 0; + gint weight_y_base, weight_x_base; + gint weight_offset; + gint pixel_offset; + gint winstart_y; + gint wghstart_y; + gint winend_y; + gint winstart_x; + gint wghstart_x; + gint winend_x; + gfloat weight; + gint source_offset; + + source_offset = oy * self->priv->width + ox; + + winstart_x = self->priv->windows[source_offset].x_window_start; + wghstart_x = self->priv->windows[source_offset].x_weight_start; + winend_x = self->priv->windows[source_offset].x_window_end; + winstart_y = self->priv->windows[source_offset].y_window_start; + wghstart_y = self->priv->windows[source_offset].y_weight_start; + winend_y = self->priv->windows[source_offset].y_window_end; + elsumm = self->priv->windows[source_offset].element_summ; + + switch (self->priv->windowtype) { + case 0: + for (iy = winstart_y; iy <= winend_y; iy++) { + pixel_offset = iy * self->priv->width; + for (ix = winstart_x; ix <= winend_x; ix++) { + mu_m += mod[pixel_offset + ix]; + } + } + mu_m = mu_m / elsumm; + mu_o = self->priv->orgmu[oy * self->priv->width + ox]; + for (iy = winstart_y; iy <= winend_y; iy++) { + pixel_offset = iy * self->priv->width; + for (ix = winstart_x; ix <= winend_x; ix++) { + tmp1 = org[pixel_offset + ix] - mu_o; + tmp2 = mod[pixel_offset + ix] - mu_m; + sigma_o += tmp1 * tmp1; + sigma_m += tmp2 * tmp2; + sigma_om += tmp1 * tmp2; + } + } + break; + case 1: + + weight_y_base = wghstart_y - winstart_y; + weight_x_base = wghstart_x - winstart_x; + + for (iy = winstart_y; iy <= winend_y; iy++) { + pixel_offset = iy * self->priv->width; + weight_offset = (weight_y_base + iy) * self->priv->windowsize + + weight_x_base; + for (ix = winstart_x; ix <= winend_x; ix++) { + weight = self->priv->weights[weight_offset + ix]; + mu_o += weight * org[pixel_offset + ix]; + mu_m += weight * mod[pixel_offset + ix]; + } + } + mu_m = mu_m / elsumm; + mu_o = self->priv->orgmu[oy * self->priv->width + ox]; + for (iy = winstart_y; iy <= winend_y; iy++) { + gfloat *weights_with_offset; + guint8 *org_with_offset, *mod_with_offset; + gfloat wt1, wt2; + pixel_offset = iy * self->priv->width; + weight_offset = (weight_y_base + iy) * self->priv->windowsize + + weight_x_base; + weights_with_offset = &self->priv->weights[weight_offset]; + org_with_offset = &org[pixel_offset]; + mod_with_offset = &mod[pixel_offset]; + for (ix = winstart_x; ix <= winend_x; ix++) { + weight = weights_with_offset[ix]; + tmp1 = org_with_offset[ix] - mu_o; + tmp2 = mod_with_offset[ix] - mu_m; + wt1 = weight * tmp1; + wt2 = weight * tmp2; + sigma_o += wt1 * tmp1; + sigma_m += wt2 * tmp2; + sigma_om += wt1 * tmp2; + } + } + break; + } + sigma_o = sqrt (sigma_o / elsumm); + sigma_m = sqrt (sigma_m / elsumm); + sigma_om = sigma_om / elsumm; + tmp1 = + (2 * mu_o * mu_m + self->priv->const1) * (2 * sigma_om + + self->priv->const2) / ((mu_o * mu_o + mu_m * mu_m + + self->priv->const1) * (sigma_o * sigma_o + sigma_m * sigma_m + + self->priv->const2)); + + /* SSIM can go negative, that's why it is + 127 + index * 128 instead of index * 255 */ + if (out) + out[oy * self->priv->width + ox] = 127 + tmp1 * 128; + *lowest = MIN (*lowest, tmp1); + *highest = MAX (*highest, tmp1); + cumulative_ssim += tmp1; + } + } + *mean = cumulative_ssim / (self->priv->width * self->priv->height); +} + +gboolean +gssim_configure (Gssim * self, gint width, gint height) +{ + if (width == self->priv->width && height == self->priv->height) + return FALSE; + + self->priv->width = width; + self->priv->height = height; + + g_free (self->priv->windows); + self->priv->windows = NULL; + + g_free (self->priv->orgmu); + self->priv->orgmu = g_new (gfloat, width * height); + + return TRUE; +} + +static void +gssim_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec) +{ + //Gssim *self = GSSIM (object); + + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gssim_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec) +{ + //Gssim *self = GSSIM (object); + + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gssim_finalize (GObject * object) +{ + Gssim *self = GSSIM (object); + void (*chain_up) (GObject *) = + ((GObjectClass *) gssim_parent_class)->finalize; + + g_free (self->priv->orgmu); + g_free (self->priv->windows); + + chain_up (object); +} + +static void +gssim_class_init (GssimClass * klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + oclass->get_property = gssim_get_property; + oclass->set_property = gssim_set_property; + oclass->finalize = gssim_finalize; +} + +static void +gssim_init (Gssim * self) +{ + self->priv = gssim_get_instance_private (self); + + self->priv->windowsize = 11; + self->priv->windowtype = 1; + self->priv->windows = NULL; + self->priv->sigma = 1.5; +} + +Gssim * +gssim_new (void) +{ + return g_object_new (GSSIM_TYPE, NULL); +} diff --git a/validate/gst-libs/gst/video/gssim.h b/validate/gst-libs/gst/video/gssim.h new file mode 100644 index 0000000000..39962ec5a6 --- /dev/null +++ b/validate/gst-libs/gst/video/gssim.h @@ -0,0 +1,62 @@ +/* GStreamer + * + * Copyright (C) 2014 Mathieu Duponchelle + * Copyright (C) 2015 Raspberry Pi Foundation + * Author: Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _GSSIM_H +#define _GSSIM_H + +#include +#include +#include +#include + +G_BEGIN_DECLS + +typedef struct _GssimPrivate GssimPrivate; + +typedef struct { + GstObject parent; + + GssimPrivate *priv; +} Gssim; + +typedef struct { + GstObjectClass parent; +} GssimClass; + +#define GSSIM_TYPE (gssim_get_type ()) +#define GSSIM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSSIM_TYPE, Gssim)) +#define GSSIM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSSIM_TYPE, GssimClass)) +#define IS_GSSIM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSSIM_TYPE)) +#define IS_GSSIM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSSIM_TYPE)) +#define GSSIM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSSIM_TYPE, GssimClass)) + +GType gssim_get_type (void); +Gssim * gssim_new (void); + +void gssim_compare (Gssim * self, guint8 * org, guint8 * mod, + guint8 * out, gfloat * mean, gfloat * lowest, + gfloat * highest); +gboolean gssim_configure (Gssim * self, gint width, gint height); + +G_END_DECLS + +#endif diff --git a/validate/gst-libs/gst/video/gstvalidatessim.c b/validate/gst-libs/gst/video/gstvalidatessim.c new file mode 100644 index 0000000000..70b5274317 --- /dev/null +++ b/validate/gst-libs/gst/video/gstvalidatessim.c @@ -0,0 +1,1031 @@ +/* GStreamer + * + * Copyright (C) 2015 Raspberry Pi Foundation + * Author: Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#include +#include +#include +#include +#include + +#include +#include "gstvalidatessim.h" +#include "gssim.h" + +#include +#include +#include +#include + +#define GST_CAT_DEFAULT gstvalidatessim_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +#define SIMILARITY_ISSUE_WITH_PREVIOUS g_quark_from_static_string ("ssim::image-not-similar-enough-with-theoretical-reference") +#define SIMILARITY_ISSUE g_quark_from_static_string ("ssim::image-not-similar-enough") +#define GENERAL_INPUT_ERROR g_quark_from_static_string ("ssim::general-file-error") +#define WRONG_FORMAT g_quark_from_static_string ("ssim::wrong-format") + +enum +{ + PROP_FIRST_PROP = 1, + PROP_RUNNER, + PROP_LAST +}; + +typedef struct +{ + GstVideoConverter *converter; + GstVideoInfo in_info; + GstVideoInfo out_info; +} SSimConverterInfo; + +struct _GstValidateSsimPrivate +{ + gint width; + gint height; + + Gssim *ssim; + + GList *converters; + GstVideoInfo out_info; + + SSimConverterInfo outconverter_info; + + gfloat min_avg_similarity; + gfloat min_lowest_similarity; + + GHashTable *ref_frames_cache; + gint fps_n, fps_d; +}; + +G_DEFINE_TYPE_WITH_CODE (GstValidateSsim, gst_validate_ssim, + GST_TYPE_OBJECT, G_ADD_PRIVATE (GstValidateSsim) + G_IMPLEMENT_INTERFACE (GST_TYPE_VALIDATE_REPORTER, NULL)); + +static void +ssim_convert_info_free (SSimConverterInfo * info) +{ + if (info->converter) + gst_video_converter_free (info->converter); + + g_slice_free (SSimConverterInfo, info); +} + +static gboolean +gst_validate_ssim_convert (GstValidateSsim * self, SSimConverterInfo * info, + GstVideoFrame * frame, GstVideoFrame * converted_frame) +{ + gboolean res = TRUE; + GstBuffer *outbuf = NULL; + + g_return_val_if_fail (info != NULL, FALSE); + + outbuf = gst_buffer_new_allocate (NULL, info->out_info.size, NULL); + if (!gst_video_frame_map (converted_frame, &info->out_info, outbuf, + GST_MAP_WRITE)) { + GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR, + "Could not map output converted_frame"); + goto fail; + } + + gst_video_converter_frame (info->converter, frame, converted_frame); + +done: + if (outbuf) + gst_buffer_unref (outbuf); + + return res; + +fail: + res = FALSE; + goto done; +} + +static gchar * +gst_validate_ssim_save_out (GstValidateSsim * self, GstBuffer * buffer, + const gchar * ref_file, const gchar * file, const gchar * outfolder) +{ + GstVideoFrame frame, converted; + + if (!g_file_test (outfolder, G_FILE_TEST_IS_DIR)) { + if (g_mkdir_with_parents (outfolder, 0755) != 0) { + + GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR, + "Could not create output directory %s", outfolder); + return NULL; + } + } + + if (self->priv->outconverter_info.converter == NULL || + self->priv->width != self->priv->outconverter_info.out_info.width || + self->priv->height != self->priv->outconverter_info.out_info.height) { + + if (self->priv->outconverter_info.converter) + gst_video_converter_free (self->priv->outconverter_info.converter); + + gst_video_info_init (&self->priv->outconverter_info.in_info); + gst_video_info_set_format (&self->priv->outconverter_info.in_info, + GST_VIDEO_FORMAT_GRAY8, self->priv->width, self->priv->height); + + gst_video_info_init (&self->priv->outconverter_info.out_info); + gst_video_info_set_format (&self->priv->outconverter_info.out_info, + GST_VIDEO_FORMAT_RGBx, self->priv->width, self->priv->height); + + self->priv->outconverter_info.converter = + gst_video_converter_new (&self->priv->outconverter_info.in_info, + &self->priv->outconverter_info.out_info, NULL); + } + + if (!gst_video_frame_map (&frame, &self->priv->outconverter_info.in_info, + buffer, GST_MAP_READ)) { + GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR, + "Could not map output frame"); + + return NULL; + } + + if (gst_validate_ssim_convert (self, &self->priv->outconverter_info, + &frame, &converted)) { + cairo_status_t status; + cairo_surface_t *surface; + gchar *bn1 = g_path_get_basename (ref_file); + gchar *bn2 = g_path_get_basename (file); + gchar *fname = g_strdup_printf ("%s.VS.%s.result.png", bn1, bn2); + gchar *outfile = g_build_path (G_DIR_SEPARATOR_S, outfolder, fname, NULL); + + surface = + cairo_image_surface_create_for_data (GST_VIDEO_FRAME_PLANE_DATA + (&converted, 0), CAIRO_FORMAT_RGB24, GST_VIDEO_FRAME_WIDTH (&converted), + GST_VIDEO_FRAME_HEIGHT (&converted), + GST_VIDEO_FRAME_PLANE_STRIDE (&converted, 0)); + + if ((status = cairo_surface_write_to_png (surface, outfile)) != + CAIRO_STATUS_SUCCESS) { + GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR, + "Could not save '%s', cairo status is '%s'", outfile, + cairo_status_to_string (status)); + } + + cairo_surface_destroy (surface); + gst_video_frame_unmap (&frame); + gst_video_frame_unmap (&converted); + g_free (bn1); + g_free (bn2); + g_free (fname); + return outfile; + } + + return NULL; +} + +static gboolean +gst_validate_ssim_configure (GstValidateSsim * self, gint width, gint height) +{ + if (width == self->priv->width && height == self->priv->height) + return FALSE; + + gssim_configure (self->priv->ssim, width, height); + + self->priv->width = width; + self->priv->height = height; + + gst_video_info_init (&self->priv->out_info); + gst_video_info_set_format (&self->priv->out_info, GST_VIDEO_FORMAT_I420, + self->priv->width, self->priv->height); + + return TRUE; +} + +static void +gst_validate_ssim_configure_converter (GstValidateSsim * self, gint index, + gboolean force, GstVideoFormat in_format, gint width, gint height) +{ + SSimConverterInfo *info = g_list_nth_data (self->priv->converters, index); + + if (!info) { + info = g_slice_new0 (SSimConverterInfo); + + self->priv->converters = + g_list_insert (self->priv->converters, info, index); + } + + if (force || info->in_info.height != height || info->in_info.width != width || + info->in_info.finfo->format != in_format) { + gst_video_info_init (&info->in_info); + gst_video_info_set_format (&info->in_info, in_format, width, height); + + if (info->converter) + gst_video_converter_free (info->converter); + + info->out_info = self->priv->out_info; + + if (gst_video_info_is_equal (&info->in_info, &info->out_info)) + info->converter = NULL; + else + info->converter = + gst_video_converter_new (&info->in_info, &info->out_info, NULL); + } +} + +static GstVideoFormat +_get_format_from_surface (cairo_surface_t * surface) +{ +#if G_BYTE_ORDER == G_BIG_ENDIAN + if (cairo_surface_get_content (surface) == CAIRO_CONTENT_COLOR_ALPHA) + return GST_VIDEO_FORMAT_BGRA; + else + return GST_VIDEO_FORMAT_BGRx; +#else + if (cairo_surface_get_content (surface) == CAIRO_CONTENT_COLOR_ALPHA) + return GST_VIDEO_FORMAT_ARGB; + else + return GST_VIDEO_FORMAT_RGBx; +#endif +} + +void +gst_validate_ssim_compare_frames (GstValidateSsim * self, + GstVideoFrame * ref_frame, GstVideoFrame * frame, GstBuffer ** outbuf, + gfloat * mean, gfloat * lowest, gfloat * highest) +{ + gboolean reconf; + guint8 *outdata = NULL; + GstMapInfo map1, map2, outmap; + + GstVideoFrame converted_frame1, converted_frame2; + SSimConverterInfo *convinfo1, *convinfo2; + + reconf = + gst_validate_ssim_configure (self, ref_frame->info.width, + ref_frame->info.height); + + gst_validate_ssim_configure_converter (self, 0, reconf, + ref_frame->info.finfo->format, ref_frame->info.width, + ref_frame->info.height); + + gst_validate_ssim_configure_converter (self, 1, reconf, + frame->info.finfo->format, frame->info.width, frame->info.height); + + convinfo1 = (SSimConverterInfo *) g_list_nth_data (self->priv->converters, 0); + if (convinfo1->converter) + gst_validate_ssim_convert (self, convinfo1, ref_frame, &converted_frame1); + else + converted_frame1 = *ref_frame; + + convinfo2 = (SSimConverterInfo *) g_list_nth_data (self->priv->converters, 1); + if (convinfo2->converter) + gst_validate_ssim_convert (self, convinfo2, frame, &converted_frame2); + else + converted_frame2 = *frame; + + if (!gst_buffer_map (converted_frame1.buffer, &map1, GST_MAP_READ)) { + GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR, + "Could not map reference frame"); + + return; + } + + if (!gst_buffer_map (converted_frame2.buffer, &map2, GST_MAP_READ)) { + gst_buffer_unmap (converted_frame1.buffer, &map1); + GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR, + "Could not map compared frame"); + + return; + } + + if (outbuf) { + *outbuf = gst_buffer_new_and_alloc (GST_ROUND_UP_4 (self->priv->width) * + self->priv->height); + if (!gst_buffer_map (*outbuf, &outmap, GST_MAP_WRITE)) { + GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR, + "Could not map output frame"); + + gst_buffer_unref (*outbuf); + gst_buffer_unmap (converted_frame1.buffer, &map1); + gst_buffer_unmap (converted_frame2.buffer, &map2); + *outbuf = NULL; + + return; + } + + outdata = outmap.data; + } + + gssim_compare (self->priv->ssim, map1.data, map2.data, outdata, mean, + lowest, highest); + + gst_buffer_unmap (ref_frame->buffer, &map1); + gst_buffer_unmap (frame->buffer, &map2); + + if (convinfo1->converter) + gst_video_frame_unmap (&converted_frame1); + if (convinfo2->converter) + gst_video_frame_unmap (&converted_frame2); + + if (outbuf) + gst_buffer_unmap (*outbuf, &outmap); +} + +static gboolean +gst_validate_ssim_get_frame_from_png (GstValidateSsim * self, const char *file, + GstVideoFrame * frame) +{ + guint8 *data; + GstBuffer *buf; + GstVideoInfo info; + cairo_surface_t *surface = NULL; + + surface = cairo_image_surface_create_from_png (file); + if (surface == NULL + || (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS)) { + GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR, "Could not open %s: %s", + file, cairo_status_to_string (cairo_surface_status (surface))); + + return FALSE; + } + + gst_video_info_init (&info); + gst_video_info_set_format (&info, + _get_format_from_surface (surface), + cairo_image_surface_get_width (surface), + cairo_image_surface_get_height (surface)); + + cairo_surface_flush (surface); + data = cairo_image_surface_get_data (surface); + buf = gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, + data, info.size, 0, info.size, surface, + (GDestroyNotify) cairo_surface_destroy); + if (!gst_video_frame_map (frame, &info, buf, GST_MAP_READ)) { + gst_buffer_unref (buf); + GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR, + "Could not map input frame"); + + return FALSE; + } + + gst_buffer_unref (buf); + + return TRUE; +} + +static gboolean +gst_validate_ssim_get_frame_from_file (GstValidateSsim * self, const char *file, + GstVideoFrame * frame) +{ + gchar *data; + gsize length; + GstBuffer *buf; + GstVideoInfo info; + GstVideoFormat format; + gint strv_length, width, height; + + gboolean res = TRUE; + gchar **splited_name = NULL, **splited_size = NULL, *strformat; + + GError *error = NULL; + + if (g_str_has_suffix (file, ".png")) { + return gst_validate_ssim_get_frame_from_png (self, file, frame); + } + + splited_name = g_strsplit (file, ".", -1); + strv_length = g_strv_length (splited_name); + + strformat = splited_name[strv_length - 1]; + format = gst_video_format_from_string (strformat); + if (format == GST_VIDEO_FORMAT_UNKNOWN) { + GST_VALIDATE_REPORT (self, WRONG_FORMAT, "Unknown format: %s", strformat); + + goto fail; + } + + splited_size = g_strsplit (splited_name[strv_length - 2], "x", -1); + if (g_strv_length (splited_size) != 2) { + GST_VALIDATE_REPORT (self, WRONG_FORMAT, + "Can not determine video size from filename: %s ", file); + + goto fail; + } + + errno = 0; + width = g_ascii_strtoull (splited_size[0], NULL, 10); + if (errno) { + GST_VALIDATE_REPORT (self, WRONG_FORMAT, + "Can not determine video size from filename: %s ", file); + + goto fail; + } + + errno = 0; + height = g_ascii_strtoull (splited_size[1], NULL, 10); + if (errno) { + GST_VALIDATE_REPORT (self, WRONG_FORMAT, + "Can not determine video size from filename: %s ", file); + + goto fail; + } + + gst_video_info_init (&info); + gst_video_info_set_format (&info, format, width, height); + + if (!g_file_get_contents (file, &data, &length, &error)) { + GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR, "Could not open %s: %s", + file, error->message); + g_error_free (error); + + goto fail; + } + + buf = gst_buffer_new_wrapped (data, length); + if (!gst_video_frame_map (frame, &info, buf, GST_MAP_READ)) { + gst_buffer_unref (buf); + GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR, + "Could not map input frame"); + + goto fail; + } + gst_buffer_unref (buf); + +done: + g_strfreev (splited_name); + g_strfreev (splited_size); + + return res; + +fail: + res = FALSE; + + goto done; +} + +static gboolean +_filename_get_timestamp (GstValidateSsim * self, const gchar * filename, + GstClockTime * ts) +{ + guint h, m, s, ns; + gchar *bname = g_path_get_basename (filename); + gchar *other = g_strdup (bname); + gboolean res = TRUE; + + if (sscanf (bname, "%" GST_VALIDATE_SSIM_TIME_FORMAT "%s", &h, &m, &s, &ns, + other) < 4) { + GST_INFO_OBJECT (self, "Can not sscanf %s", bname); + + goto fail; + } + + *ts = (h * 3600 + m * 60 + s) * GST_SECOND + ns; + +done: + g_free (other); + g_free (bname); + return res; + +fail: + res = FALSE; + goto done; +} + +typedef struct +{ + gchar *path; + GstClockTime ts; +} Frame; + +static void +_free_frame (Frame * frame) +{ + g_free (frame->path); +} + +static gint +_sort_frames (Frame * a, Frame * b) +{ + if (a->ts < b->ts) + return -1; + + if (a->ts == b->ts) + return 0; + + return 1; +} + +static Frame * +_find_frame (GstValidateSsim * self, GArray * frames, GstClockTime ts, + gboolean get_next) +{ + guint i; + Frame *lframe = &g_array_index (frames, Frame, 0); + + if (self->priv->fps_n) { + gint64 frame_number = gst_util_uint64_scale (ts, self->priv->fps_n, + self->priv->fps_d * GST_SECOND); + + if (frames->len < frame_number) + return NULL; + + return &g_array_index (frames, Frame, frame_number); + } + + if (frames->len == 1) { + Frame *iframe = &g_array_index (frames, Frame, 0); + + if (iframe->ts == ts) + return iframe; + + return NULL; + } + + for (i = 1; i < frames->len; i++) { + Frame *iframe = &g_array_index (frames, Frame, i); + + if (ts >= lframe->ts && iframe->ts > ts) { + if (get_next) + return iframe; + + return lframe; + } else if (i + 1 == frames->len) { + return iframe; + } + + lframe = iframe; + } + + return NULL; +} + +static GArray * +_get_ref_frame_cache (GstValidateSsim * self, const gchar * ref_file) +{ + GFile *ref_dir_file = NULL; + GFileInfo *info; + GFileEnumerator *fenum; + GArray *frames = NULL; + gchar *ref_dir = NULL; + + ref_dir = g_path_get_dirname (ref_file); + + frames = g_hash_table_lookup (self->priv->ref_frames_cache, ref_file); + if (frames) + goto done; + + ref_dir_file = g_file_new_for_path (ref_dir); + if (!(fenum = g_file_enumerate_children (ref_dir_file, + "standard::*", G_FILE_QUERY_INFO_NONE, NULL, NULL))) { + GST_INFO ("%s is not a folder", ref_dir); + + goto done; + } + + for (info = g_file_enumerator_next_file (fenum, NULL, NULL); + info; info = g_file_enumerator_next_file (fenum, NULL, NULL)) { + Frame iframe; + const gchar *display_name = g_file_info_get_display_name (info); + + if (!_filename_get_timestamp (self, display_name, &iframe.ts)) { + g_object_unref (info); + continue; + } + + iframe.path = g_build_path (G_DIR_SEPARATOR_S, + ref_dir, g_file_info_get_name (info), NULL); + + g_object_unref (info); + + if (!frames) { + frames = g_array_new (TRUE, TRUE, sizeof (Frame)); + + g_array_set_clear_func (frames, (GDestroyNotify) _free_frame); + } + g_array_append_val (frames, iframe); + } + g_object_unref (fenum); + + if (frames) { + g_array_sort (frames, (GCompareFunc) _sort_frames); + + g_hash_table_insert (self->priv->ref_frames_cache, g_strdup (ref_dir), + frames); + } + +done: + g_clear_object (&ref_dir_file); + g_free (ref_dir); + + return frames; +} + +static gchar * +_get_ref_file_path (GstValidateSsim * self, const gchar * ref_file, + const gchar * file, gboolean get_next) +{ + Frame *frame; + GArray *frames; + gchar *real_ref_file = NULL; + GstClockTime file_ts; + + if (!g_strrstr (ref_file, "*")) + return g_strdup (ref_file); + + if (!_filename_get_timestamp (self, file, &file_ts)) { + goto done; + } + + frames = _get_ref_frame_cache (self, ref_file); + if (frames) { + frame = _find_frame (self, frames, file_ts, get_next); + + if (frame) + real_ref_file = g_strdup (frame->path); + } + +done: + + return real_ref_file; +} + +static gboolean +gst_validate_ssim_compare_image_file (GstValidateSsim * self, + const gchar * ref_file, const gchar * file, gfloat * mean, gfloat * lowest, + gfloat * highest, const gchar * outfolder) +{ + GstBuffer *outbuf = NULL, **poutbuf = NULL; + gboolean res = TRUE; + GstVideoFrame ref_frame, frame; + gchar *real_ref_file = NULL; + gchar *output_failure_image = NULL, *failure_info = NULL; + + real_ref_file = _get_ref_file_path (self, ref_file, file, FALSE); + + if (!real_ref_file) { + GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR, + "Could find ref file for %s", ref_file); + goto fail; + } + + if (!gst_validate_ssim_get_frame_from_file (self, real_ref_file, &ref_frame)) + goto fail; + + + if (!gst_validate_ssim_get_frame_from_file (self, file, &frame)) { + gst_video_frame_unmap (&ref_frame); + + goto fail; + } + + if (outfolder) { + poutbuf = &outbuf; + } + + gst_validate_ssim_compare_frames (self, &ref_frame, &frame, + poutbuf, mean, lowest, highest); + + if (*mean < self->priv->min_avg_similarity) { + GstClockTime ref_ts, f_ts; + + gst_video_frame_unmap (&ref_frame); + gst_video_frame_unmap (&frame); + + _filename_get_timestamp (self, real_ref_file, &ref_ts); + _filename_get_timestamp (self, file, &f_ts); + + if (g_strcmp0 (ref_file, real_ref_file) && ref_ts != f_ts) { + gchar *tmpref = real_ref_file; + + real_ref_file = _get_ref_file_path (self, ref_file, file, TRUE); + + GST_VALIDATE_REPORT (self, SIMILARITY_ISSUE_WITH_PREVIOUS, + "\nComparing %s with %s failed, (mean %f " + " min %f), checking next %s\n", + tmpref, file, *mean, *lowest, real_ref_file); + + g_free (tmpref); + + res = gst_validate_ssim_compare_image_file (self, + real_ref_file, file, mean, lowest, highest, outfolder); + goto done; + } + + if (outbuf) + output_failure_image = + gst_validate_ssim_save_out (self, outbuf, real_ref_file, file, + outfolder); + + if (output_failure_image) + failure_info = + g_strdup_printf (" (See %s to check differences in images)", + output_failure_image); + + GST_VALIDATE_REPORT (self, SIMILARITY_ISSUE, + "Average similarity '%f' between %s and %s inferior" + " than the minimum average: %f%s", *mean, + real_ref_file, file, self->priv->min_avg_similarity, failure_info); + + goto fail; + } + + if (*lowest < self->priv->min_lowest_similarity) { + if (outbuf) + output_failure_image = + gst_validate_ssim_save_out (self, outbuf, real_ref_file, file, + outfolder); + + if (output_failure_image) + failure_info = + g_strdup_printf (" (See %s to check differences in images)", + output_failure_image); + + GST_VALIDATE_REPORT (self, SIMILARITY_ISSUE, + "Lowest similarity '%f' between %s and %s inferior" + " than the minimum lowest similarity: %f%s", *lowest, + real_ref_file, file, self->priv->min_lowest_similarity, failure_info); + + gst_video_frame_unmap (&ref_frame); + gst_video_frame_unmap (&frame); + + goto fail; + } + + gst_video_frame_unmap (&ref_frame); + gst_video_frame_unmap (&frame); + +done: + + g_free (failure_info); + g_free (output_failure_image); + g_free (real_ref_file); + if (outbuf) + gst_buffer_unref (outbuf); + + return res; + +fail: + res = FALSE; + + goto done; +} + +static gboolean +_check_directory (GstValidateSsim * self, const gchar * ref_dir, + const gchar * compared_dir, gfloat * mean, gfloat * lowest, + gfloat * highest, const gchar * outfolder) +{ + gint nfiles = 0, nnotfound = 0, nfailures = 0; + gboolean res = TRUE; + GFileInfo *info; + GFileEnumerator *fenum; + gfloat min_avg = 1.0, min_min = 1.0, total_avg = 0; + GFile *file = g_file_new_for_path (ref_dir); + + if (!(fenum = g_file_enumerate_children (file, + "standard::*", G_FILE_QUERY_INFO_NONE, NULL, NULL))) { + GST_INFO ("%s is not a folder", ref_dir); + res = FALSE; + + goto done; + } + + for (info = g_file_enumerator_next_file (fenum, NULL, NULL); + info; info = g_file_enumerator_next_file (fenum, NULL, NULL)) { + + if (g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR || + g_file_info_get_file_type (info) == G_FILE_TYPE_SYMBOLIC_LINK) { + gchar *compared_file = g_build_path (G_DIR_SEPARATOR_S, + compared_dir, g_file_info_get_name (info), NULL); + gchar *ref_file = NULL; + + if (!g_file_test (compared_file, G_FILE_TEST_IS_REGULAR)) { + GST_INFO_OBJECT (self, "Could not find file %s", compared_file); + nnotfound++; + res = FALSE; + } else { + + ref_file = + g_build_path (G_DIR_SEPARATOR_S, ref_dir, + g_file_info_get_name (info), NULL); + if (!gst_validate_ssim_compare_image_files (self, ref_file, + compared_file, mean, lowest, highest, outfolder)) { + nfailures++; + res = FALSE; + } else { + nfiles++; + } + } + + min_avg = MIN (min_avg, *mean); + min_min = MIN (min_min, *lowest); + total_avg += *mean; + gst_validate_printf (NULL, + "\r", + g_file_info_get_display_name (info), + GST_TIME_ARGS (GST_CLOCK_TIME_NONE), + *mean, *lowest, nfiles, nfailures, nnotfound); + + g_free (compared_file); + g_free (ref_file); + } + + g_object_unref (info); + } + + if (nfiles == 0) { + gst_validate_printf (NULL, "\nNo files to verify.\n"); + } else { + gst_validate_printf (NULL, + "\nAverage similarity: %f, min_avg: %f, min_min: %f\n", + total_avg / nfiles, min_avg, min_min); + } + +done: + gst_object_unref (file); + if (fenum) + gst_object_unref (fenum); + + return res; +} + +gboolean +gst_validate_ssim_compare_image_files (GstValidateSsim * self, + const gchar * ref_file, const gchar * file, gfloat * mean, gfloat * lowest, + gfloat * highest, const gchar * outfolder) +{ + if (g_file_test (ref_file, G_FILE_TEST_IS_DIR)) { + if (!g_file_test (file, G_FILE_TEST_IS_DIR)) { + GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR, + "%s is a directory but %s is not", ref_file, file); + + return FALSE; + } + + return _check_directory (self, ref_file, file, mean, lowest, highest, + outfolder); + } else { + return gst_validate_ssim_compare_image_file (self, ref_file, file, mean, + lowest, highest, outfolder); + } +} + +static void +gst_validate_ssim_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + case PROP_RUNNER: + /* we assume the runner is valid as long as this scenario is, + * no ref taken */ + g_value_set_object (value, + gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (object))); + break; + default: + break; + } +} + +static void +gst_validate_ssim_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + case PROP_RUNNER: + /* we assume the runner is valid as long as this scenario is, + * no ref taken */ + gst_validate_reporter_set_runner (GST_VALIDATE_REPORTER (object), + g_value_get_object (value)); + break; + default: + break; + } +} + +static void +gst_validate_ssim_dispose (GObject * object) +{ + GstValidateSsim *self = GST_VALIDATE_SSIM (object); + void (*chain_up) (GObject *) = + ((GObjectClass *) gst_validate_ssim_parent_class)->dispose; + + gst_object_unref (self->priv->ssim); + + chain_up (object); +} + +static void +gst_validate_ssim_finalize (GObject * object) +{ + GstValidateSsim *self = GST_VALIDATE_SSIM (object); + void (*chain_up) (GObject *) = + ((GObjectClass *) gst_validate_ssim_parent_class)->finalize; + + g_list_free_full (self->priv->converters, + (GDestroyNotify) ssim_convert_info_free); + + if (self->priv->outconverter_info.converter) + gst_video_converter_free (self->priv->outconverter_info.converter); + g_hash_table_unref (self->priv->ref_frames_cache); + + chain_up (object); +} + +static gpointer +_register_issues (gpointer data) +{ + gst_validate_issue_register (gst_validate_issue_new_full (SIMILARITY_ISSUE, + "Compared images were not similar enough", + "The images checker detected that the images" + " it is comparing do not have the similarity" + " level defined with min-avg-similarity or" + " min-lowest-similarity", GST_VALIDATE_REPORT_LEVEL_CRITICAL, + GST_VALIDATE_ISSUE_FLAGS_FULL_DETAILS | + GST_VALIDATE_ISSUE_FLAGS_NO_BACKTRACE)); + + gst_validate_issue_register (gst_validate_issue_new + (SIMILARITY_ISSUE_WITH_PREVIOUS, + "Comparison with theoretical reference image failed", + " In a case where we have reference frames with the following" + " timestamps: [0.00, 0.10, 0.20, 0.30], comparing a frame with" + " 0.05 as a timestamp will be done with the first frame." + " If this fails, a ssim::image-not-similar-enough-with-theoretical-reference" + " warning is issued and the system then tries with the second reference frame.", + GST_VALIDATE_REPORT_LEVEL_WARNING)); + + gst_validate_issue_register (gst_validate_issue_new (GENERAL_INPUT_ERROR, + "Something went wrong handling image files for ssim comparison", + "An error occurred when working with input files", + GST_VALIDATE_REPORT_LEVEL_CRITICAL)); + + gst_validate_issue_register (gst_validate_issue_new (WRONG_FORMAT, + "The format or dimensions of the compared images do not match", + "The format or dimensions of the compared images do not match", + GST_VALIDATE_REPORT_LEVEL_CRITICAL)); + + return NULL; +} + +static void +gst_validate_ssim_class_init (GstValidateSsimClass * klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + static GOnce _once = G_ONCE_INIT; + + GST_DEBUG_CATEGORY_INIT (gstvalidatessim_debug, "validatessim", 0, + "Validate ssim plugin"); + + oclass->get_property = gst_validate_ssim_get_property; + oclass->set_property = gst_validate_ssim_set_property; + oclass->dispose = gst_validate_ssim_dispose; + oclass->finalize = gst_validate_ssim_finalize; + + g_once (&_once, _register_issues, NULL); + + g_object_class_install_property (oclass, PROP_RUNNER, + g_param_spec_object ("validate-runner", "VALIDATE Runner", + "The Validate runner to report errors to", + GST_TYPE_VALIDATE_RUNNER, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); +} + +static void +gst_validate_ssim_init (GstValidateSsim * self) +{ + self->priv = gst_validate_ssim_get_instance_private (self); + + self->priv->ssim = gssim_new (); + self->priv->ref_frames_cache = g_hash_table_new_full (g_str_hash, + g_str_equal, g_free, (GDestroyNotify) g_array_unref); +} + +GstValidateSsim * +gst_validate_ssim_new (GstValidateRunner * runner, + gfloat min_avg_similarity, gfloat min_lowest_similarity, + gint fps_n, gint fps_d) +{ + GstValidateSsim *self = + g_object_new (GST_VALIDATE_SSIM_TYPE, "validate-runner", runner, NULL); + + self->priv->min_avg_similarity = min_avg_similarity; + self->priv->min_lowest_similarity = min_lowest_similarity; + self->priv->fps_n = fps_n; + self->priv->fps_d = fps_d; + + gst_validate_reporter_set_name (GST_VALIDATE_REPORTER (self), + g_strdup ("gst-validate-images-checker")); + + return self; +} diff --git a/validate/gst-libs/gst/video/gstvalidatessim.h b/validate/gst-libs/gst/video/gstvalidatessim.h new file mode 100644 index 0000000000..52c7e55e28 --- /dev/null +++ b/validate/gst-libs/gst/video/gstvalidatessim.h @@ -0,0 +1,77 @@ +/* GStreamer + * + * Copyright (C) 2014 Mathieu Duponchelle + * Copyright (C) 2015 Raspberry Pi Foundation + * Author: Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _GST_VALIDATE_SSIM_H +#define _GST_VALIDATE_SSIM_H + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include + +G_BEGIN_DECLS + +typedef struct _GstValidateSsimPrivate GstValidateSsimPrivate; + +typedef struct { + GstObject parent; + + GstValidateSsimPrivate *priv; +} GstValidateSsim; + +typedef struct { + GstObjectClass parent; +} GstValidateSsimClass; + +#define GST_VALIDATE_SSIM_TIME_FORMAT "u-%02u-%02u.%09u" + +#define GST_VALIDATE_SSIM_TYPE (gst_validate_ssim_get_type ()) +#define GST_VALIDATE_SSIM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_VALIDATE_SSIM_TYPE, GstValidateSsim)) +#define GST_VALIDATE_SSIM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_VALIDATE_SSIM_TYPE, GstValidateSsimClass)) +#define IS_GST_VALIDATE_SSIM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_VALIDATE_SSIM_TYPE)) +#define IS_GST_VALIDATE_SSIM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_VALIDATE_SSIM_TYPE)) +#define GST_VALIDATE_SSIM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_VALIDATE_SSIM_TYPE, GstValidateSsimClass)) + +GType gst_validate_ssim_get_type (void); + +GstValidateSsim * gst_validate_ssim_new (GstValidateRunner *runner, + gfloat min_avg_similarity, + gfloat min_lowest_similarity, + gint fps_n, + gint fps_d); + +gboolean gst_validate_ssim_compare_image_files (GstValidateSsim *self, const gchar *ref_file, + const gchar * file, gfloat * mean, gfloat * lowest, + gfloat * highest, const gchar *outfolder); + +void gst_validate_ssim_compare_frames (GstValidateSsim * self, GstVideoFrame *ref_frame, + GstVideoFrame *frame, GstBuffer **outbuf, + gfloat * mean, gfloat * lowest, gfloat * highest); + +G_END_DECLS + +#endif diff --git a/validate/gst-libs/gst/video/meson.build b/validate/gst-libs/gst/video/meson.build new file mode 100644 index 0000000000..c4a84da576 --- /dev/null +++ b/validate/gst-libs/gst/video/meson.build @@ -0,0 +1,18 @@ +validate_video_dep = dependency('', required: false) +cairo_dep = dependency('cairo-png', required: false, fallback: 'cairo') + +if cairo_dep.found() + video = static_library( + 'gstvalidatevideo', + 'gstvalidatessim.c', 'gssim.c', + include_directories : inc_dirs, + dependencies : [gst_dep, gst_video_dep, gst_pbutils_dep, glib_dep, cairo_dep, gio_dep, + mathlib], + ) + validate_video_dep = declare_dependency( + link_with : video, + include_directories : inc_dirs, + dependencies : [gst_dep, gst_video_dep, gst_pbutils_dep, glib_dep, cairo_dep, gio_dep, + mathlib], + ) +endif diff --git a/validate/gst-libs/meson.build b/validate/gst-libs/meson.build new file mode 100644 index 0000000000..22b4c2d430 --- /dev/null +++ b/validate/gst-libs/meson.build @@ -0,0 +1,2 @@ +subdir('gst') + diff --git a/validate/gst/meson.build b/validate/gst/meson.build new file mode 100644 index 0000000000..f0c7ebe804 --- /dev/null +++ b/validate/gst/meson.build @@ -0,0 +1,2 @@ +subdir('validate') +subdir('overrides') diff --git a/validate/gst/overrides/gst-validate-default-overrides.c b/validate/gst/overrides/gst-validate-default-overrides.c new file mode 100644 index 0000000000..62871cf0ec --- /dev/null +++ b/validate/gst/overrides/gst-validate-default-overrides.c @@ -0,0 +1,48 @@ +/* GStreamer + * + * Copyright (C) 2013 Collabora Ltd. + * Author: Vincent Penquerc'h + * + * gst-validate-default-overrides.c - Test overrides + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include + +/* public symbol */ +GST_PLUGIN_EXPORT int gst_validate_create_overrides (void); + +int +gst_validate_create_overrides (void) +{ + GstValidateOverride *o; + + /* Some random test override. Will moan on: + gst-launch videotestsrc num-buffers=10 ! video/x-raw-yuv ! fakesink */ + o = gst_validate_override_new (); + gst_validate_override_change_severity (o, + g_quark_from_string ("caps::is-missing-field"), + GST_VALIDATE_REPORT_LEVEL_CRITICAL); + gst_validate_override_register_by_name ("capsfilter0", o); + g_object_unref (o); + return 1; +} diff --git a/validate/gst/overrides/meson.build b/validate/gst/overrides/meson.build new file mode 100644 index 0000000000..37024e1413 --- /dev/null +++ b/validate/gst/overrides/meson.build @@ -0,0 +1,9 @@ +library('gstvalidate-default-overrides-1.0', + sources: 'gst-validate-default-overrides.c', + version : libversion, + soversion : soversion, + darwin_versions : osxversion, + include_directories : [inc_dirs], + install: true, + c_args : [gst_c_args] + ['-D_GNU_SOURCE'], + dependencies : validate_dep) diff --git a/validate/gst/validate/flow/formatting.c b/validate/gst/validate/flow/formatting.c new file mode 100644 index 0000000000..773611844e --- /dev/null +++ b/validate/gst/validate/flow/formatting.c @@ -0,0 +1,436 @@ +/* GStreamer + * + * Copyright (C) 2018-2019 Igalia S.L. + * Copyright (C) 2018 Metrological Group B.V. + * Author: Alicia Boya García + * + * formatting.c: Functions used by validateflow to get string + * representations of buffers. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "formatting.h" + +#include +#include +#include +#include +#include + +#include "../../gst/validate/gst-validate-utils.h" + +typedef void (*Uint64Formatter) (gchar * dest, guint64 time); +G_LOCK_DEFINE (checksums_as_id_lock); +static GstStructure *checksums_as_id = NULL; + +#define CONSTIFY(strv) ((const gchar * const *) strv) + +static gboolean +use_field (const gchar * field, gchar ** logged, gchar ** ignored) +{ + if (logged) + return g_strv_contains (CONSTIFY (logged), field); + + if (ignored) + return !g_strv_contains (CONSTIFY (ignored), field); + + return TRUE; +} + + +void +format_time (gchar * dest_str, guint64 time) +{ + if (GST_CLOCK_TIME_IS_VALID (time)) { + g_sprintf (dest_str, "%" GST_TIME_FORMAT, GST_TIME_ARGS (time)); + } else { + strcpy (dest_str, "none"); + } +} + +static void +format_number (gchar * dest_str, guint64 number) +{ + g_sprintf (dest_str, "%" G_GUINT64_FORMAT, number); +} + +gchar * +validate_flow_format_segment (const GstSegment * segment, + gchar ** logged_fields, gchar ** ignored_fields) +{ + Uint64Formatter uint64_format; + gchar *segment_str; + gchar *parts[12]; + GString *format; + gchar start_str[32], offset_str[32], stop_str[32], time_str[32], base_str[32], + position_str[32], duration_str[32]; + int parts_index = 0; + + uint64_format = + segment->format == GST_FORMAT_TIME ? format_time : format_number; + uint64_format (start_str, segment->start); + uint64_format (offset_str, segment->offset); + uint64_format (stop_str, segment->stop); + uint64_format (time_str, segment->time); + uint64_format (base_str, segment->base); + uint64_format (position_str, segment->position); + uint64_format (duration_str, segment->duration); + + format = g_string_new (gst_format_get_name (segment->format)); + format = g_string_ascii_up (format); + + if (use_field ("format", logged_fields, ignored_fields)) + parts[parts_index++] = g_strdup_printf ("format=%s", format->str); + + if (use_field ("start", logged_fields, ignored_fields)) + parts[parts_index++] = g_strdup_printf ("start=%s", start_str); + + if (use_field ("offset", logged_fields, ignored_fields)) + parts[parts_index++] = g_strdup_printf ("offset=%s", offset_str); + + if (use_field ("stop", logged_fields, ignored_fields)) + parts[parts_index++] = g_strdup_printf ("stop=%s", stop_str); + + if (segment->rate != 1.0) + parts[parts_index++] = g_strdup_printf ("rate=%f", segment->rate); + if (segment->applied_rate != 1.0) + parts[parts_index++] = + g_strdup_printf ("applied_rate=%f", segment->applied_rate); + + if (segment->flags && use_field ("flags", logged_fields, ignored_fields)) + parts[parts_index++] = g_strdup_printf ("flags=0x%02x", segment->flags); + + if (use_field ("time", logged_fields, ignored_fields)) + parts[parts_index++] = g_strdup_printf ("time=%s", time_str); + if (use_field ("base", logged_fields, ignored_fields)) + parts[parts_index++] = g_strdup_printf ("base=%s", base_str); + if (use_field ("position", logged_fields, ignored_fields)) + parts[parts_index++] = g_strdup_printf ("position=%s", position_str); + if (GST_CLOCK_TIME_IS_VALID (segment->duration) + && use_field ("duration", logged_fields, ignored_fields)) + parts[parts_index++] = g_strdup_printf ("duration=%s", duration_str); + parts[parts_index] = NULL; + + segment_str = g_strjoinv (", ", parts); + + while (parts_index > 0) + g_free (parts[--parts_index]); + g_string_free (format, TRUE); + + return segment_str; +} + +typedef struct +{ + GList *fields; + + gchar **wanted_fields; + gchar **ignored_fields; +} StructureValues; + +static gboolean +structure_set_fields (GQuark field_id, GValue * value, StructureValues * data) +{ + const gchar *field = g_quark_to_string (field_id); + + if (data->ignored_fields + && g_strv_contains ((const gchar **) data->ignored_fields, field)) + return TRUE; + + if (data->wanted_fields + && !g_strv_contains ((const gchar **) data->wanted_fields, field)) + return TRUE; + + data->fields = g_list_prepend (data->fields, (gchar *) field); + + return TRUE; +} + +static GstStructure * +validate_flow_structure_cleanup (const GstStructure * structure, + gchar ** wanted_fields, gchar ** ignored_fields) +{ + GstStructure *nstructure; + StructureValues d = { + .fields = NULL, + .wanted_fields = wanted_fields, + .ignored_fields = ignored_fields, + }; + + gst_structure_foreach (structure, + (GstStructureForeachFunc) structure_set_fields, &d); + d.fields = g_list_sort (d.fields, (GCompareFunc) g_ascii_strcasecmp); + nstructure = gst_structure_new_empty (gst_structure_get_name (structure)); + for (GList * tmp = d.fields; tmp; tmp = tmp->next) { + gchar *field = tmp->data; + + gst_structure_set_value (nstructure, field, + gst_structure_get_value (structure, field)); + } + + g_list_free (d.fields); + + return nstructure; +} + +gchar * +validate_flow_format_caps (const GstCaps * caps, gchar ** wanted_fields) +{ + guint i; + GstCaps *new_caps = gst_caps_new_empty (); + gchar *caps_str; + + /* A single GstCaps can contain several caps structures (although only one is + * used in most cases). We will print them separated with spaces. */ + for (i = 0; i < gst_caps_get_size (caps); i++) { + GstStructure *structure = + validate_flow_structure_cleanup (gst_caps_get_structure (caps, i), + wanted_fields, NULL); + + gst_caps_append_structure_full (new_caps, structure, + gst_caps_features_copy (gst_caps_get_features (caps, i))); + } + + caps_str = gst_caps_to_string (new_caps); + gst_caps_unref (new_caps); + + return caps_str; +} + + +static gchar * +buffer_get_flags_string (GstBuffer * buffer) +{ + GFlagsClass *flags_class = + G_FLAGS_CLASS (g_type_class_ref (gst_buffer_flags_get_type ())); + GstBufferFlags flags = GST_BUFFER_FLAGS (buffer); + GString *string = NULL; + + while (1) { + GFlagsValue *value = g_flags_get_first_value (flags_class, flags); + if (!value) + break; + + if (string == NULL) + string = g_string_new (NULL); + else + g_string_append (string, " "); + + g_string_append (string, value->value_nick); + flags &= ~value->value; + } + + return (string != NULL) ? g_string_free (string, FALSE) : NULL; +} + +/* Returns a newly-allocated string describing the metas on this buffer, or NULL */ +static gchar * +buffer_get_meta_string (GstBuffer * buffer) +{ + gpointer state = NULL; + GstMeta *meta; + GString *s = NULL; + + while ((meta = gst_buffer_iterate_meta (buffer, &state))) { + const gchar *desc = g_type_name (meta->info->type); + + if (s == NULL) + s = g_string_new (NULL); + else + g_string_append (s, ", "); + + if (meta->info->api == GST_VIDEO_REGION_OF_INTEREST_META_API_TYPE) { + GstVideoRegionOfInterestMeta *roi = (GstVideoRegionOfInterestMeta *) meta; + g_string_append_printf (s, + "GstVideoRegionOfInterestMeta[x=%" G_GUINT32_FORMAT ", y=%" + G_GUINT32_FORMAT ", width=%" G_GUINT32_FORMAT ", height=%" + G_GUINT32_FORMAT "]", roi->x, roi->y, roi->w, roi->h); + } else { + g_string_append (s, desc); + } + } + + return (s != NULL) ? g_string_free (s, FALSE) : NULL; +} + +gchar * +validate_flow_format_buffer (GstBuffer * buffer, gint checksum_type, + GstStructure * logged_fields_struct, GstStructure * ignored_fields_struct) +{ + gchar *flags_str, *meta_str, *buffer_str; + gchar *buffer_parts[7]; + int buffer_parts_index = 0; + GstMapInfo map; + gchar **logged_fields = + logged_fields_struct ? gst_validate_utils_get_strv (logged_fields_struct, + "buffer") : NULL; + gchar **ignored_fields = + ignored_fields_struct ? + gst_validate_utils_get_strv (ignored_fields_struct, "buffer") : NULL; + + if (checksum_type != CHECKSUM_TYPE_NONE || (logged_fields + && g_strv_contains (CONSTIFY (logged_fields), "checksum"))) { + if (!gst_buffer_map (buffer, &map, GST_MAP_READ)) { + GST_ERROR ("Buffer could not be mapped."); + } else if (checksum_type == CHECKSUM_TYPE_CONTENT_HEX) { + gint i; + GString *content = g_string_new ("content="); + + for (i = 0; i < map.size; i++) { + if (i) + g_string_append_c (content, ' '); + g_string_append_printf (content, "0x%02x", map.data[i]); + } + + buffer_parts[buffer_parts_index++] = g_string_free (content, FALSE); + } else { + gchar *sum = + g_compute_checksum_for_data (checksum_type == + CHECKSUM_TYPE_AS_ID ? G_CHECKSUM_SHA1 : checksum_type, map.data, + map.size); + gst_buffer_unmap (buffer, &map); + + if (checksum_type == CHECKSUM_TYPE_AS_ID) { + gint id; + + G_LOCK (checksums_as_id_lock); + if (!checksums_as_id) + checksums_as_id = gst_structure_new_empty ("checksums-id"); + if (!gst_structure_get_int (checksums_as_id, sum, &id)) { + id = gst_structure_n_fields (checksums_as_id); + gst_structure_set (checksums_as_id, sum, G_TYPE_INT, id, NULL); + } + G_UNLOCK (checksums_as_id_lock); + + buffer_parts[buffer_parts_index++] = + g_strdup_printf ("content-id=%d", id); + } else { + buffer_parts[buffer_parts_index++] = + g_strdup_printf ("checksum=%s", sum); + } + g_free (sum); + } + } + + if (GST_CLOCK_TIME_IS_VALID (buffer->dts) + && use_field ("dts", logged_fields, ignored_fields)) { + gchar time_str[32]; + format_time (time_str, buffer->dts); + buffer_parts[buffer_parts_index++] = g_strdup_printf ("dts=%s", time_str); + } + + if (GST_CLOCK_TIME_IS_VALID (buffer->pts) + && use_field ("pts", logged_fields, ignored_fields)) { + gchar time_str[32]; + format_time (time_str, buffer->pts); + buffer_parts[buffer_parts_index++] = g_strdup_printf ("pts=%s", time_str); + } + + if (GST_CLOCK_TIME_IS_VALID (buffer->duration) + && use_field ("dur", logged_fields, ignored_fields)) { + gchar time_str[32]; + format_time (time_str, buffer->duration); + buffer_parts[buffer_parts_index++] = g_strdup_printf ("dur=%s", time_str); + } + + flags_str = buffer_get_flags_string (buffer); + if (flags_str && use_field ("flags", logged_fields, ignored_fields)) { + buffer_parts[buffer_parts_index++] = + g_strdup_printf ("flags=%s", flags_str); + } + + meta_str = buffer_get_meta_string (buffer); + if (meta_str && use_field ("meta", logged_fields, ignored_fields)) + buffer_parts[buffer_parts_index++] = g_strdup_printf ("meta=%s", meta_str); + + buffer_parts[buffer_parts_index] = NULL; + buffer_str = + buffer_parts_index > 0 ? g_strjoinv (", ", + buffer_parts) : g_strdup ("(empty)"); + + g_free (meta_str); + g_free (flags_str); + while (buffer_parts_index > 0) + g_free (buffer_parts[--buffer_parts_index]); + + return buffer_str; +} + +gchar * +validate_flow_format_event (GstEvent * event, + const gchar * const *caps_properties, + GstStructure * logged_fields_struct, + GstStructure * ignored_fields_struct, + const gchar * const *ignored_event_types, + const gchar * const *logged_event_types) +{ + const gchar *event_type; + gchar *structure_string; + gchar *event_string; + gchar **ignored_fields; + gchar **logged_fields; + + event_type = gst_event_type_get_name (GST_EVENT_TYPE (event)); + + if (logged_event_types && !g_strv_contains (logged_event_types, event_type)) + return NULL; + + if (ignored_event_types && g_strv_contains (ignored_event_types, event_type)) + return NULL; + + logged_fields = + logged_fields_struct ? gst_validate_utils_get_strv (logged_fields_struct, + event_type) : NULL; + ignored_fields = + ignored_fields_struct ? + gst_validate_utils_get_strv (ignored_fields_struct, event_type) : NULL; + if (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) { + const GstSegment *segment; + gst_event_parse_segment (event, &segment); + structure_string = + validate_flow_format_segment (segment, logged_fields, ignored_fields); + } else if (GST_EVENT_TYPE (event) == GST_EVENT_CAPS) { + GstCaps *caps; + gst_event_parse_caps (event, &caps); + + structure_string = + validate_flow_format_caps (caps, + logged_fields ? logged_fields : (gchar **) caps_properties); + /* FIXME: Remove spurious `;` and regenerate all the expectation files */ + event_string = g_strdup_printf ("%s: %s;", event_type, structure_string); + goto done; + } else if (!gst_event_get_structure (event)) { + structure_string = g_strdup ("(no structure)"); + } else { + GstStructure *structure = + validate_flow_structure_cleanup (gst_event_get_structure (event), + logged_fields, ignored_fields); + structure_string = gst_structure_to_string (structure); + gst_structure_free (structure); + } + + event_string = g_strdup_printf ("%s: %s", event_type, structure_string); +done: + g_strfreev (logged_fields); + g_strfreev (ignored_fields); + g_free (structure_string); + return event_string; +} diff --git a/validate/gst/validate/flow/formatting.h b/validate/gst/validate/flow/formatting.h new file mode 100644 index 0000000000..3c0dee250b --- /dev/null +++ b/validate/gst/validate/flow/formatting.h @@ -0,0 +1,42 @@ +/* GStreamer + * + * Copyright (C) 2018-2019 Igalia S.L. + * Copyright (C) 2018 Metrological Group B.V. + * Author: Alicia Boya García + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_VALIDATE_FLOW_FORMATTING_H__ +#define __GST_VALIDATE_FLOW_FORMATTING_H__ + +#include + +#define CHECKSUM_TYPE_AS_ID -1 +#define CHECKSUM_TYPE_NONE -2 +#define CHECKSUM_TYPE_CONTENT_HEX -3 + +void format_time(gchar* dest_str, guint64 time); + +gchar* validate_flow_format_segment(const GstSegment* segment, gchar** logged_fields, gchar** ignored_fields); + +gchar* validate_flow_format_caps (const GstCaps* caps, gchar **wanted_fields); + +gchar* validate_flow_format_buffer(GstBuffer* buffer, gboolean add_checksum, GstStructure* logged_fields_struct, GstStructure* ignored_fields_struct); + +gchar* validate_flow_format_event(GstEvent* event, const gchar* const* caps_properties, GstStructure* logged_event_fields, GstStructure* ignored_event_fields, const gchar* const* ignored_event_types, const gchar* const* logged_event_types); + +#endif // __GST_VALIDATE_FLOW_FORMATTING_H__ diff --git a/validate/gst/validate/flow/gstvalidateflow.c b/validate/gst/validate/flow/gstvalidateflow.c new file mode 100644 index 0000000000..0662a315ec --- /dev/null +++ b/validate/gst/validate/flow/gstvalidateflow.c @@ -0,0 +1,700 @@ +/* GStreamer + * + * Copyright (C) 2018-2019 Igalia S.L. + * Copyright (C) 2018 Metrological Group B.V. + * Author: Alicia Boya García + * + * gstvalidateflow.c: A plugin to record streams and match them to + * expectation files. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "../validate.h" +#include "../gst-validate-utils.h" +#include "../gst-validate-report.h" +#include "../gst-validate-internal.h" +#include "formatting.h" +#include +#include +#include +#include + +#include "gstvalidateflow.h" + +#define VALIDATE_FLOW_MISMATCH g_quark_from_static_string ("validateflow::mismatch") +#define VALIDATE_FLOW_NOT_ATTACHED g_quark_from_static_string ("validateflow::not-attached") + +typedef enum _ValidateFlowMode +{ + VALIDATE_FLOW_MODE_WRITING_EXPECTATIONS, + VALIDATE_FLOW_MODE_WRITING_ACTUAL_RESULTS +} ValidateFlowMode; + +#define GST_TYPE_VALIDATE_FLOW_CHECKSUM_TYPE (validate_flow_checksum_type_get_type ()) +static GType +validate_flow_checksum_type_get_type (void) +{ + static GType gtype = 0; + + if (gtype == 0) { + static const GEnumValue values[] = { + {CHECKSUM_TYPE_NONE, "NONE", "none"}, + {CHECKSUM_TYPE_AS_ID, "AS-ID", "as-id"}, + {CHECKSUM_TYPE_CONTENT_HEX, "raw-hex", "raw-hex"}, + {G_CHECKSUM_MD5, "MD5", "md5"}, + {G_CHECKSUM_SHA1, "SHA-1", "sha1"}, + {G_CHECKSUM_SHA256, "SHA-256", "sha256"}, + {G_CHECKSUM_SHA512, "SHA-512", "sha512"}, + {0, NULL, NULL}, + }; + + gtype = g_enum_register_static ("ValidateFlowChecksumType", values); + } + return gtype; +} + +struct _ValidateFlowOverride +{ + GstValidateOverride parent; + + const gchar *pad_name; + gboolean record_buffers; + gint checksum_type; + gchar *expectations_dir; + gchar *actual_results_dir; + gboolean error_writing_file; + gchar **caps_properties; + GstStructure *ignored_fields; + GstStructure *logged_fields; + + gchar **logged_event_types; + gchar **ignored_event_types; + + gchar *expectations_file_path; + gchar *actual_results_file_path; + ValidateFlowMode mode; + gboolean was_attached; + GstStructure *config; + + /* output_file will refer to the expectations file if it did not exist, + * or to the actual results file otherwise. */ + gchar *output_file_path; + FILE *output_file; + GMutex output_file_mutex; + +}; + +GList *all_overrides = NULL; + +static void validate_flow_override_finalize (GObject * object); +static void validate_flow_override_attached (GstValidateOverride * override); +static void _runner_set (GObject * object, GParamSpec * pspec, + gpointer user_data); +static void runner_stopping (GstValidateRunner * runner, + ValidateFlowOverride * flow); + +#define VALIDATE_TYPE_FLOW_OVERRIDE validate_flow_override_get_type () +G_DEFINE_TYPE (ValidateFlowOverride, validate_flow_override, + GST_TYPE_VALIDATE_OVERRIDE); + +void +validate_flow_override_init (ValidateFlowOverride * self) +{ +} + +void +validate_flow_override_class_init (ValidateFlowOverrideClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GstValidateOverrideClass *override_class = + GST_VALIDATE_OVERRIDE_CLASS (klass); + + object_class->finalize = validate_flow_override_finalize; + override_class->attached = validate_flow_override_attached; + + g_assert (gst_validate_is_initialized ()); + + gst_validate_issue_register (gst_validate_issue_new + (VALIDATE_FLOW_MISMATCH, + "The recorded log does not match the expectation file.", + "The recorded log does not match the expectation file.", + GST_VALIDATE_REPORT_LEVEL_CRITICAL)); + + gst_validate_issue_register (gst_validate_issue_new + (VALIDATE_FLOW_NOT_ATTACHED, + "The pad to monitor was never attached.", + "The pad to monitor was never attached.", + GST_VALIDATE_REPORT_LEVEL_CRITICAL)); +} + +/* *INDENT-OFF* */ +G_GNUC_PRINTF (2, 0) +/* *INDENT-ON* */ + +static void +validate_flow_override_vprintf (ValidateFlowOverride * flow, const char *format, + va_list ap) +{ + g_mutex_lock (&flow->output_file_mutex); + if (!flow->error_writing_file && vfprintf (flow->output_file, format, ap) < 0) { + GST_ERROR_OBJECT (flow, "Writing to file %s failed", + flow->output_file_path); + flow->error_writing_file = TRUE; + } + g_mutex_unlock (&flow->output_file_mutex); +} + +/* *INDENT-OFF* */ +G_GNUC_PRINTF (2, 3) +/* *INDENT-ON* */ + +static void +validate_flow_override_printf (ValidateFlowOverride * flow, const char *format, + ...) +{ + va_list ap; + va_start (ap, format); + validate_flow_override_vprintf (flow, format, ap); + va_end (ap); +} + +static void +validate_flow_override_event_handler (GstValidateOverride * override, + GstValidateMonitor * pad_monitor, GstEvent * event) +{ + ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (override); + gchar *event_string; + + if (flow->error_writing_file) + return; + + event_string = validate_flow_format_event (event, + (const gchar * const *) flow->caps_properties, + flow->logged_fields, + flow->ignored_fields, + (const gchar * const *) flow->ignored_event_types, + (const gchar * const *) flow->logged_event_types); + + if (event_string) { + validate_flow_override_printf (flow, "event %s\n", event_string); + g_free (event_string); + } +} + +static void +validate_flow_override_buffer_handler (GstValidateOverride * override, + GstValidateMonitor * pad_monitor, GstBuffer * buffer) +{ + ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (override); + gchar *buffer_str; + + if (flow->error_writing_file || !flow->record_buffers) + return; + + buffer_str = validate_flow_format_buffer (buffer, flow->checksum_type, + flow->logged_fields, flow->ignored_fields); + validate_flow_override_printf (flow, "buffer: %s\n", buffer_str); + g_free (buffer_str); +} + +static gchar * +make_safe_file_name (const gchar * name) +{ + gchar *ret = g_strdup (name); + gchar *c; + for (c = ret; *c; c++) { + switch (*c) { + case '<': + case '>': + case ':': + case '"': + case '/': + case '\\': + case '|': + case '?': + case '*': + *c = '-'; + break; + } + } + return ret; +} + +static ValidateFlowOverride * +validate_flow_override_new (GstStructure * config) +{ + ValidateFlowOverride *flow; + GstValidateOverride *override; + gboolean use_checksum = FALSE; + gchar *ignored_fields = NULL, *logged_fields; + const GValue *tmpval; + + flow = g_object_new (VALIDATE_TYPE_FLOW_OVERRIDE, NULL); + flow->config = config; + + GST_OBJECT_FLAG_SET (flow, GST_OBJECT_FLAG_MAY_BE_LEAKED); + override = GST_VALIDATE_OVERRIDE (flow); + + /* pad: Name of the pad where flowing buffers and events will be monitorized. */ + flow->pad_name = gst_structure_get_string (config, "pad"); + if (!flow->pad_name) { + gst_validate_error_structure (config, + "pad property is mandatory, not found in %" GST_PTR_FORMAT, config); + } + + /* record-buffers: Whether buffers will be written to the expectation log. */ + flow->record_buffers = FALSE; + gst_structure_get_boolean (config, "record-buffers", &flow->record_buffers); + + flow->checksum_type = CHECKSUM_TYPE_NONE; + gst_structure_get_boolean (config, "buffers-checksum", &use_checksum); + + if (use_checksum) { + flow->checksum_type = G_CHECKSUM_SHA1; + } else { + const gchar *checksum_type = + gst_structure_get_string (config, "buffers-checksum"); + + if (checksum_type) { + if (!gst_validate_utils_enum_from_str + (GST_TYPE_VALIDATE_FLOW_CHECKSUM_TYPE, checksum_type, + (guint *) & flow->checksum_type)) + gst_validate_error_structure (config, + "Invalid value for buffers-checksum: %s", checksum_type); + } + } + + if (flow->checksum_type != CHECKSUM_TYPE_NONE) + flow->record_buffers = TRUE; + + /* caps-properties: Caps events can include many dfferent properties, but + * many of these may be irrelevant for some tests. If this option is set, + * only the listed properties will be written to the expectation log. */ + flow->caps_properties = + gst_validate_utils_get_strv (config, "caps-properties"); + + flow->logged_event_types = + gst_validate_utils_get_strv (config, "logged-event-types"); + flow->ignored_event_types = + gst_validate_utils_get_strv (config, "ignored-event-types"); + + tmpval = gst_structure_get_value (config, "ignored-fields"); + if (tmpval) { + if (!G_VALUE_HOLDS_STRING (tmpval)) { + gst_validate_error_structure (config, + "Invalid value type for `ignored-fields`: '%s' instead of 'string'", + G_VALUE_TYPE_NAME (tmpval)); + } + ignored_fields = (gchar *) g_value_get_string (tmpval); + } + + if (ignored_fields) { + ignored_fields = g_strdup_printf ("ignored,%s", ignored_fields); + flow->ignored_fields = gst_structure_new_from_string (ignored_fields); + if (!flow->ignored_fields) + gst_validate_error_structure (config, + "Could not parse 'ignored-event-fields' structure: `%s`", + ignored_fields); + g_free (ignored_fields); + } else { + flow->ignored_fields = + gst_structure_new_from_string ("ignored,stream-start={stream-id}"); + } + + if (!gst_structure_has_field (flow->ignored_fields, "stream-start")) + gst_structure_set (flow->ignored_fields, "stream-start", + G_TYPE_STRING, "stream-id", NULL); + + logged_fields = (gchar *) gst_structure_get_string (config, "logged-fields"); + if (logged_fields) { + logged_fields = g_strdup_printf ("logged,%s", logged_fields); + flow->logged_fields = gst_structure_new_from_string (logged_fields); + if (!flow->logged_fields) + gst_validate_error_structure (config, + "Could not parse 'logged-fields' %s", logged_fields); + g_free (logged_fields); + } else { + flow->logged_fields = NULL; + } + + /* expectations-dir: Path to the directory where the expectations will be + * written if they don't exist, relative to the current working directory. + * By default the current working directory is used. */ + flow->expectations_dir = + g_strdup (gst_structure_get_string (config, "expectations-dir")); + if (!flow->expectations_dir) + flow->expectations_dir = g_strdup ("."); + + /* actual-results-dir: Path to the directory where the events will be + * recorded. The expectation file will be compared to this. */ + flow->actual_results_dir = + g_strdup (gst_structure_get_string (config, "actual-results-dir")); + if (!flow->actual_results_dir) + flow->actual_results_dir = g_strdup ("."); + + { + gchar *pad_name_safe = make_safe_file_name (flow->pad_name); + gchar *expectations_file_name = + g_strdup_printf ("log-%s-expected", pad_name_safe); + gchar *actual_results_file_name = + g_strdup_printf ("log-%s-actual", pad_name_safe); + flow->expectations_file_path = + g_build_path (G_DIR_SEPARATOR_S, flow->expectations_dir, + expectations_file_name, NULL); + flow->actual_results_file_path = + g_build_path (G_DIR_SEPARATOR_S, flow->actual_results_dir, + actual_results_file_name, NULL); + g_free (expectations_file_name); + g_free (actual_results_file_name); + g_free (pad_name_safe); + } + + flow->was_attached = FALSE; + + gst_validate_override_register_by_name (flow->pad_name, override); + + override->buffer_handler = validate_flow_override_buffer_handler; + override->buffer_probe_handler = validate_flow_override_buffer_handler; + override->event_handler = validate_flow_override_event_handler; + + g_signal_connect (flow, "notify::validate-runner", + G_CALLBACK (_runner_set), NULL); + + return flow; +} + +static void +validate_flow_setup_files (ValidateFlowOverride * flow, gint default_generate) +{ + gint local_generate_expectations = -1; + gboolean generate_if_doesn_exit = default_generate == -1; + gboolean exists = + g_file_test (flow->expectations_file_path, G_FILE_TEST_EXISTS); + + if (generate_if_doesn_exit) { + gst_structure_get_boolean (flow->config, "generate-expectations", + &local_generate_expectations); + generate_if_doesn_exit = local_generate_expectations == -1; + } + + if ((!default_generate || !local_generate_expectations) && !exists) { + gst_validate_error_structure (flow->config, "Not writing expectations and" + " configured expectation file %s doesn't exist in config:\n > %" + GST_PTR_FORMAT, flow->expectations_file_path, flow->config); + } + + if (exists && local_generate_expectations != 1 && default_generate != 1) { + flow->mode = VALIDATE_FLOW_MODE_WRITING_ACTUAL_RESULTS; + flow->output_file_path = g_strdup (flow->actual_results_file_path); + gst_validate_printf (NULL, "**-> Checking expectations file: '%s'**\n", + flow->expectations_file_path); + } else { + flow->mode = VALIDATE_FLOW_MODE_WRITING_EXPECTATIONS; + flow->output_file_path = g_strdup (flow->expectations_file_path); + gst_validate_printf (NULL, "**-> Writing expectations file: '%s'**\n", + flow->expectations_file_path); + } + + { + gchar *directory_path = g_path_get_dirname (flow->output_file_path); + if (g_mkdir_with_parents (directory_path, 0755) < 0) { + gst_validate_abort ("Could not create directory tree: %s Reason: %s", + directory_path, g_strerror (errno)); + } + g_free (directory_path); + } + + flow->output_file = fopen (flow->output_file_path, "w"); + if (!flow->output_file) + gst_validate_abort ("Could not open for writing: %s", + flow->output_file_path); + +} + +static void +_runner_set (GObject * object, GParamSpec * pspec, gpointer user_data) +{ + ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (object); + GstValidateRunner *runner = + gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (flow)); + + g_signal_connect (runner, "stopping", G_CALLBACK (runner_stopping), flow); + gst_object_unref (runner); +} + +static void +validate_flow_override_attached (GstValidateOverride * override) +{ + ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (override); + flow->was_attached = TRUE; +} + +static void +run_diff (const gchar * expected_file, const gchar * actual_file) +{ + GError *error = NULL; + GSubprocess *process = + g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE, &error, "diff", "-u", + "--", expected_file, actual_file, NULL); + gchar *stdout_text = NULL; + + g_subprocess_communicate_utf8 (process, NULL, NULL, &stdout_text, NULL, + &error); + if (!error) { + gboolean colored = gst_validate_has_colored_output (); + GSubprocess *process2; + gchar *fname = NULL; + gint f = g_file_open_tmp ("XXXXXX.diff", &fname, NULL); + + if (f > 0) { + gchar *tmpstdout; + g_file_set_contents (fname, stdout_text, -1, NULL); + close (f); + + process2 = + g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE, &error, "bat", "-l", + "diff", "--paging", "never", "--color", colored ? "always" : "never", + fname, NULL); + + g_subprocess_communicate_utf8 (process2, NULL, NULL, &tmpstdout, NULL, + &error); + if (!error) { + g_free (stdout_text); + stdout_text = tmpstdout; + } else { + colored = FALSE; + GST_DEBUG ("Could not use bat: %s", error->message); + g_clear_error (&error); + } + g_clear_object (&process2); + g_free (fname); + } + + fprintf (stderr, "%s%s%s\n", + !colored ? "``` diff\n" : "", stdout_text, !colored ? "\n```" : ""); + } else { + fprintf (stderr, "Cannot show more details, failed to run diff: %s", + error->message); + g_error_free (error); + } + + g_object_unref (process); + g_free (stdout_text); +} + +static const gchar * +_line_to_show (gchar ** lines, gsize i) +{ + if (lines[i] == NULL) { + return ""; + } else if (*lines[i] == '\0') { + if (lines[i + 1] != NULL) + /* skip blank lines for reporting purposes (e.g. before CHECKPOINT) */ + return lines[i + 1]; + else + /* last blank line in the file */ + return ""; + } else { + return lines[i]; + } +} + +static void +show_mismatch_error (ValidateFlowOverride * flow, gchar ** lines_expected, + gchar ** lines_actual, gsize line_index) +{ + const gchar *line_expected = _line_to_show (lines_expected, line_index); + const gchar *line_actual = _line_to_show (lines_actual, line_index); + + GST_VALIDATE_REPORT (flow, VALIDATE_FLOW_MISMATCH, + "Mismatch error in pad %s, line %" G_GSIZE_FORMAT + ". Expected:\n%s\nActual:\n%s\n", flow->pad_name, line_index + 1, + line_expected, line_actual); + + run_diff (flow->expectations_file_path, flow->actual_results_file_path); +} + +static void +runner_stopping (GstValidateRunner * runner, ValidateFlowOverride * flow) +{ + gchar **lines_expected, **lines_actual; + gsize i = 0; + + fclose (flow->output_file); + flow->output_file = NULL; + + if (!flow->was_attached) { + GST_VALIDATE_REPORT (flow, VALIDATE_FLOW_NOT_ATTACHED, + "The test ended without the pad ever being attached: %s", + flow->pad_name); + return; + } + + if (flow->mode == VALIDATE_FLOW_MODE_WRITING_EXPECTATIONS) { + gst_validate_skip_test ("wrote expectation files for %s.\n", + flow->pad_name); + + return; + } + + { + gchar *contents; + GError *error = NULL; + g_file_get_contents (flow->expectations_file_path, &contents, NULL, &error); + if (error) { + gst_validate_abort ("Failed to open expectations file: %s Reason: %s", + flow->expectations_file_path, error->message); + } + lines_expected = g_strsplit (contents, "\n", 0); + g_free (contents); + } + + { + gchar *contents; + GError *error = NULL; + g_file_get_contents (flow->actual_results_file_path, &contents, NULL, + &error); + if (error) { + gst_validate_abort ("Failed to open actual results file: %s Reason: %s", + flow->actual_results_file_path, error->message); + } + lines_actual = g_strsplit (contents, "\n", 0); + g_free (contents); + } + + gst_validate_printf (flow, "Checking that flow %s matches expected flow %s\n", + flow->expectations_file_path, flow->actual_results_file_path); + + for (i = 0; lines_expected[i] && lines_actual[i]; i++) { + if (g_strcmp0 (lines_expected[i], lines_actual[i])) { + show_mismatch_error (flow, lines_expected, lines_actual, i); + goto stop; + } + } + gst_validate_printf (flow, "OK\n"); + if (!lines_expected[i] && lines_actual[i]) { + show_mismatch_error (flow, lines_expected, lines_actual, i); + goto stop; + } else if (lines_expected[i] && !lines_actual[i]) { + show_mismatch_error (flow, lines_expected, lines_actual, i); + goto stop; + } + +stop: + g_strfreev (lines_expected); + g_strfreev (lines_actual); +} + +static void +validate_flow_override_finalize (GObject * object) +{ + ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (object); + + all_overrides = g_list_remove (all_overrides, flow); + g_free (flow->actual_results_dir); + g_free (flow->actual_results_file_path); + g_free (flow->expectations_dir); + g_free (flow->expectations_file_path); + g_free (flow->output_file_path); + if (flow->output_file) + fclose (flow->output_file); + g_strfreev (flow->caps_properties); + g_strfreev (flow->logged_event_types); + g_strfreev (flow->ignored_event_types); + if (flow->ignored_fields) + gst_structure_free (flow->ignored_fields); + + G_OBJECT_CLASS (validate_flow_override_parent_class)->finalize (object); +} + +static gboolean +_execute_checkpoint (GstValidateScenario * scenario, GstValidateAction * action) +{ + GList *i; + gchar *checkpoint_name = + g_strdup (gst_structure_get_string (action->structure, "text")); + + for (i = all_overrides; i; i = i->next) { + ValidateFlowOverride *flow = (ValidateFlowOverride *) i->data; + + if (checkpoint_name) + validate_flow_override_printf (flow, "\nCHECKPOINT: %s\n\n", + checkpoint_name); + else + validate_flow_override_printf (flow, "\nCHECKPOINT\n\n"); + } + + g_free (checkpoint_name); + return TRUE; +} + +gboolean +gst_validate_flow_init () +{ + GList *tmp; + gint default_generate = -1; + GList *config_list = gst_validate_get_config ("validateflow"); + + if (!config_list) + return TRUE; + + for (tmp = config_list; tmp; tmp = tmp->next) { + GstStructure *config = tmp->data; + ValidateFlowOverride *flow; + + if (gst_structure_has_field (config, "generate-expectations") && + !gst_structure_has_field (config, "pad")) { + if (!gst_structure_get_boolean (config, "generate-expectations", + &default_generate)) { + gst_validate_error_structure (config, + "Field 'generate-expectations' should be a boolean"); + } + + continue; + } + + flow = validate_flow_override_new (config); + all_overrides = g_list_append (all_overrides, flow); + } + g_list_free (config_list); + + for (tmp = all_overrides; tmp; tmp = tmp->next) + validate_flow_setup_files (tmp->data, default_generate); + +/* *INDENT-OFF* */ + gst_validate_register_action_type ("checkpoint", "validateflow", + _execute_checkpoint, ((GstValidateActionParameter []) + { + { + .name = "text", + .description = "Text that will be logged in validateflow", + .mandatory = FALSE, + .types = "string" + }, + {NULL} + }), + "Prints a line of text in validateflow logs so that it's easy to distinguish buffers and events ocurring before or after a given action.", + GST_VALIDATE_ACTION_TYPE_NONE); +/* *INDENT-ON* */ + + return TRUE; +} diff --git a/validate/gst/validate/flow/gstvalidateflow.h b/validate/gst/validate/flow/gstvalidateflow.h new file mode 100644 index 0000000000..065c99eb5b --- /dev/null +++ b/validate/gst/validate/flow/gstvalidateflow.h @@ -0,0 +1,34 @@ +/* GStreamer + * + * Copyright (C) 2018-2019 Igalia S.L. + * Copyright (C) 2018 Metrological Group B.V. + * Author: Alicia Boya García + * + * gstvalidateflow.c: A plugin to record streams and match them to + * expectation files. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include "../../gst/validate/validate.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (ValidateFlowOverride, validate_flow_override, + VALIDATE, FLOW_OVERRIDE, GstValidateOverride); + +G_END_DECLS diff --git a/validate/gst/validate/flow/meson.build b/validate/gst/validate/flow/meson.build new file mode 100644 index 0000000000..701e9b258b --- /dev/null +++ b/validate/gst/validate/flow/meson.build @@ -0,0 +1,9 @@ +shared_library('gstvalidateflow', + 'gstvalidateflow.c', 'formatting.c', + include_directories : inc_dirs, + c_args: ['-DHAVE_CONFIG_H'], + install: true, + install_dir: validate_plugins_install_dir, + dependencies : [gst_dep, gst_pbutils_dep, gio_dep], + link_with : [gstvalidate] + ) diff --git a/validate/gst/validate/gettext.h b/validate/gst/validate/gettext.h new file mode 100644 index 0000000000..59902b35ee --- /dev/null +++ b/validate/gst/validate/gettext.h @@ -0,0 +1,69 @@ +/* Convenience header for conditional use of GNU . + Copyright (C) 1995-1998, 2000-2002 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published + by the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. */ + +#ifndef _LIBGETTEXT_H +#define _LIBGETTEXT_H 1 + +/* NLS can be disabled through the configure --disable-nls option. */ +#ifdef ENABLE_NLS + +/* Get declarations of GNU message catalog functions. */ +# include + +#else + +/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which + chokes if dcgettext is defined as a macro. So include it now, to make + later inclusions of a NOP. We don't include + as well because people using "gettext.h" will not include , + and also including would fail on SunOS 4, whereas + is OK. */ +#if defined(__sun) +# include +#endif + +/* Disabled NLS. + The casts to 'const char *' serve the purpose of producing warnings + for invalid uses of the value returned from these functions. + On pre-ANSI systems without 'const', the config.h file is supposed to + contain "#define const". */ +# define gettext(Msgid) ((const char *) (Msgid)) +# define dgettext(Domainname, Msgid) ((const char *) (Msgid)) +# define dcgettext(Domainname, Msgid, Category) ((const char *) (Msgid)) +# define ngettext(Msgid1, Msgid2, N) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define dngettext(Domainname, Msgid1, Msgid2, N) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define textdomain(Domainname) ((const char *) (Domainname)) +# define bindtextdomain(Domainname, Dirname) ((const char *) (Dirname)) +# define bind_textdomain_codeset(Domainname, Codeset) ((const char *) (Codeset)) + +#endif + +/* A pseudo function call that serves as a marker for the automated + extraction of messages, but does not call gettext(). The run-time + translation is done at a different place in the code. + The argument, String, should be a literal string. Concatenated strings + and other string expressions won't work. + The macro's expansion is not parenthesized, so that it is suitable as + initializer for static 'char[]' or 'const char[]' variables. */ +#define gettext_noop(String) String + +#endif /* _LIBGETTEXT_H */ diff --git a/validate/gst/validate/gst-validate-bin-monitor.c b/validate/gst/validate/gst-validate-bin-monitor.c new file mode 100644 index 0000000000..ab2c82c2a0 --- /dev/null +++ b/validate/gst/validate/gst-validate-bin-monitor.c @@ -0,0 +1,370 @@ +/* GStreamer + * + * Copyright (C) 2013 Collabora Ltd. + * Author: Thiago Sousa Santos + * + * gst-validate-bin-monitor.c - Validate BinMonitor class + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gst-validate-internal.h" +#include "gst-validate-bin-monitor.h" +#include "gst-validate-monitor-factory.h" + +#define PRINT_POSITION_TIMEOUT 250 + +/** + * SECTION:gst-validate-bin-monitor + * @short_description: Class that wraps a #GstBin for Validate checks + * + * TODO + */ + +enum +{ + PROP_0, + PROP_HANDLES_STATE, + PROP_LAST +}; + +#define gst_validate_bin_monitor_parent_class parent_class +G_DEFINE_TYPE (GstValidateBinMonitor, gst_validate_bin_monitor, + GST_TYPE_VALIDATE_ELEMENT_MONITOR); + +static void +gst_validate_bin_monitor_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void +gst_validate_bin_monitor_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void +gst_validate_bin_monitor_wrap_element (GstValidateBinMonitor * monitor, + GstElement * element); +static gboolean gst_validate_bin_monitor_setup (GstValidateMonitor * monitor); + +static void +_validate_bin_element_added (GstBin * bin, GstElement * pad, + GstValidateBinMonitor * monitor); + +static void +_validate_bin_element_removed (GstBin * bin, GstElement * element, + GstValidateBinMonitor * monitor); + +static void +gst_validate_bin_set_media_descriptor (GstValidateMonitor * monitor, + GstValidateMediaDescriptor * media_descriptor) +{ + GList *tmp; + + GST_VALIDATE_MONITOR_LOCK (monitor); + for (tmp = GST_VALIDATE_BIN_MONITOR_CAST (monitor)->element_monitors; tmp; + tmp = tmp->next) { + GstValidateMonitor *sub_monitor = (GstValidateMonitor *) tmp->data; + gst_validate_monitor_set_media_descriptor (sub_monitor, media_descriptor); + } + GST_VALIDATE_MONITOR_UNLOCK (monitor); + + GST_VALIDATE_MONITOR_CLASS (parent_class)->set_media_descriptor (monitor, + media_descriptor); +} + +static void +gst_validate_bin_monitor_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + switch (prop_id) { + case PROP_HANDLES_STATE: + g_assert_not_reached (); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_validate_bin_monitor_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstValidateBinMonitor *monitor; + + monitor = GST_VALIDATE_BIN_MONITOR_CAST (object); + + switch (prop_id) { + case PROP_HANDLES_STATE: + if (monitor->scenario == NULL) + g_value_set_boolean (value, FALSE); + else + g_object_get_property (G_OBJECT (monitor->scenario), "handles-states", + value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +purge_and_unref_reporter (gpointer data) +{ + GstValidateReporter *reporter = data; + + gst_validate_reporter_purge_reports (reporter); + g_object_unref (reporter); +} + +static void +gst_validate_bin_monitor_dispose (GObject * object) +{ + GstValidateBinMonitor *monitor = GST_VALIDATE_BIN_MONITOR_CAST (object); + GstElement *bin = + GST_ELEMENT (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR_CAST + (monitor))); + + if (bin) { + if (monitor->element_added_id) + g_signal_handler_disconnect (bin, monitor->element_added_id); + if (monitor->element_removed_id) + g_signal_handler_disconnect (bin, monitor->element_removed_id); + gst_object_unref (bin); + } + + if (monitor->scenario) { + gst_validate_reporter_purge_reports (GST_VALIDATE_REPORTER + (monitor->scenario)); + gst_clear_object (&monitor->scenario); + } + + g_list_free_full (monitor->element_monitors, purge_and_unref_reporter); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + + +static void +gst_validate_bin_monitor_class_init (GstValidateBinMonitorClass * klass) +{ + GObjectClass *gobject_class; + GstValidateMonitorClass *validatemonitor_class; + + gobject_class = G_OBJECT_CLASS (klass); + validatemonitor_class = GST_VALIDATE_MONITOR_CLASS_CAST (klass); + + gobject_class->get_property = gst_validate_bin_monitor_get_property; + gobject_class->set_property = gst_validate_bin_monitor_set_property; + gobject_class->dispose = gst_validate_bin_monitor_dispose; + + g_object_class_install_property (gobject_class, PROP_HANDLES_STATE, + g_param_spec_boolean ("handles-states", "Handles state", + "True if the application should not set handle the first state change " + " False if it is application responsibility", + FALSE, G_PARAM_READABLE)); + + validatemonitor_class->setup = gst_validate_bin_monitor_setup; + validatemonitor_class->set_media_descriptor = + gst_validate_bin_set_media_descriptor; +} + +static void +gst_validate_bin_monitor_init (GstValidateBinMonitor * bin_monitor) +{ +} + +/** + * gst_validate_bin_monitor_new: + * @bin: (transfer none): a #GstBin to run Validate on + */ +GstValidateBinMonitor * +gst_validate_bin_monitor_new (GstBin * bin, GstValidateRunner * runner, + GstValidateMonitor * parent) +{ + GstValidateBinMonitor *monitor = + g_object_new (GST_TYPE_VALIDATE_BIN_MONITOR, "object", + bin, "validate-runner", runner, "validate-parent", parent, NULL); + GstObject *target = + gst_validate_monitor_get_target (GST_VALIDATE_MONITOR (monitor)); + + if (target == NULL) { + g_object_unref (monitor); + return NULL; + } + gst_object_unref (target); + + return monitor; +} + +static void +gst_validate_bin_child_added_overrides (GstValidateMonitor * monitor, + GstElement * element) +{ + GList *iter; + + GST_VALIDATE_MONITOR_OVERRIDES_LOCK (monitor); + for (iter = GST_VALIDATE_MONITOR_OVERRIDES (monitor).head; iter; + iter = g_list_next (iter)) { + GstValidateOverride *override = iter->data; + + gst_validate_override_element_added_handler (override, monitor, element); + } + GST_VALIDATE_MONITOR_OVERRIDES_UNLOCK (monitor); +} + +static gboolean +gst_validate_bin_monitor_setup (GstValidateMonitor * monitor) +{ + GstIterator *iterator; + gboolean done; + GstElement *element; + GstValidateBinMonitor *bin_monitor = GST_VALIDATE_BIN_MONITOR_CAST (monitor); + GstBin *bin = GST_BIN_CAST (gst_validate_monitor_get_target (monitor)); + + if (!GST_IS_BIN (bin)) { + GST_WARNING_OBJECT (monitor, "Trying to create bin monitor with other " + "type of object"); + goto fail; + } + + GST_DEBUG_OBJECT (bin_monitor, "Setting up monitor for bin %" GST_PTR_FORMAT, + bin); + + if (g_object_get_data ((GObject *) bin, "validate-monitor")) { + GST_DEBUG_OBJECT (bin_monitor, + "Bin already has a validate-monitor associated"); + goto fail; + } + + bin_monitor->element_added_id = + g_signal_connect (bin, "element-added", + G_CALLBACK (_validate_bin_element_added), monitor); + + bin_monitor->element_removed_id = + g_signal_connect (bin, "element-removed", + G_CALLBACK (_validate_bin_element_removed), monitor); + + iterator = gst_bin_iterate_elements (bin); + done = FALSE; + while (!done) { + GValue value = { 0, }; + + switch (gst_iterator_next (iterator, &value)) { + case GST_ITERATOR_OK: + element = g_value_get_object (&value); + gst_validate_bin_monitor_wrap_element (bin_monitor, element); + g_value_reset (&value); + break; + case GST_ITERATOR_RESYNC: + /* TODO how to handle this? */ + gst_iterator_resync (iterator); + break; + case GST_ITERATOR_ERROR: + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (iterator); + gst_object_unref (bin); + + return GST_VALIDATE_MONITOR_CLASS (parent_class)->setup (monitor); + +fail: + if (bin) + gst_object_unref (bin); + return FALSE; +} + +static void +gst_validate_bin_monitor_wrap_element (GstValidateBinMonitor * monitor, + GstElement * element) +{ + GstValidateElementMonitor *element_monitor; + GstValidateRunner *runner = + gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (monitor)); + + GST_DEBUG_OBJECT (monitor, "Wrapping element %s", GST_ELEMENT_NAME (element)); + + element_monitor = + GST_VALIDATE_ELEMENT_MONITOR_CAST (gst_validate_monitor_factory_create + (GST_OBJECT_CAST (element), runner, GST_VALIDATE_MONITOR_CAST (monitor))); + g_return_if_fail (element_monitor != NULL); + + GST_VALIDATE_MONITOR_CAST (element_monitor)->verbosity = + GST_VALIDATE_MONITOR_CAST (monitor)->verbosity; + gst_validate_bin_child_added_overrides (GST_VALIDATE_MONITOR (monitor), + element); + + if (GST_VALIDATE_MONITOR_CAST (monitor)->verbosity & + GST_VALIDATE_VERBOSITY_NEW_ELEMENTS) + gst_validate_printf (NULL, "(element-added) %s added to %s\n", + GST_ELEMENT_NAME (element), + gst_validate_reporter_get_name (GST_VALIDATE_REPORTER (monitor))); + + GST_VALIDATE_MONITOR_LOCK (monitor); + monitor->element_monitors = g_list_prepend (monitor->element_monitors, + element_monitor); + GST_VALIDATE_MONITOR_UNLOCK (monitor); + + gst_object_unref (runner); +} + +static void +_validate_bin_element_added (GstBin * bin, GstElement * element, + GstValidateBinMonitor * monitor) +{ + GstObject *target = + gst_validate_monitor_get_target (GST_VALIDATE_MONITOR (monitor)); + + g_return_if_fail (GST_ELEMENT_CAST (target) == GST_ELEMENT_CAST (bin)); + + gst_object_unref (target); + gst_validate_bin_monitor_wrap_element (monitor, element); +} + +static void +_validate_bin_element_removed (GstBin * bin, GstElement * element, + GstValidateBinMonitor * monitor) +{ + if (GST_VALIDATE_MONITOR_CAST (monitor)->verbosity & + GST_VALIDATE_VERBOSITY_NEW_ELEMENTS) + gst_validate_printf (NULL, "(element-removed) %s removed from %s\n", + GST_ELEMENT_NAME (element), + gst_validate_reporter_get_name (GST_VALIDATE_REPORTER (monitor))); +} + +/** + * gst_validate_bin_monitor_get_scenario: + * @monitor: A #GstValidateBinMonitor + * + * Returns: (transfer full) (nullable): The #GstValidateScenario being executed + * under @monitor watch + * + * Since: 1.20 + */ +GstValidateScenario * +gst_validate_bin_monitor_get_scenario (GstValidateBinMonitor * monitor) +{ + if (monitor->scenario) + return gst_object_ref (monitor->scenario); + + return NULL; +} diff --git a/validate/gst/validate/gst-validate-bin-monitor.h b/validate/gst/validate/gst-validate-bin-monitor.h new file mode 100644 index 0000000000..300161d360 --- /dev/null +++ b/validate/gst/validate/gst-validate-bin-monitor.h @@ -0,0 +1,91 @@ +/* GStreamer + * Copyright (C) 2013 Thiago Santos + * + * gst-validate-bin-monitor.h - Validate BinMonitor class + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_VALIDATE_BIN_MONITOR_H__ +#define __GST_VALIDATE_BIN_MONITOR_H__ + +#include +#include +#include +#include +#include + +G_BEGIN_DECLS + +#ifndef __GI_SCANNER__ +#define GST_TYPE_VALIDATE_BIN_MONITOR (gst_validate_bin_monitor_get_type ()) +#define GST_IS_VALIDATE_BIN_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_VALIDATE_BIN_MONITOR)) +#define GST_IS_VALIDATE_BIN_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_VALIDATE_BIN_MONITOR)) +#define GST_VALIDATE_BIN_MONITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_VALIDATE_BIN_MONITOR, GstValidateBinMonitorClass)) +#define GST_VALIDATE_BIN_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_VALIDATE_BIN_MONITOR, GstValidateBinMonitor)) +#define GST_VALIDATE_BIN_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_VALIDATE_BIN_MONITOR, GstValidateBinMonitorClass)) +#define GST_VALIDATE_BIN_MONITOR_CAST(obj) ((GstValidateBinMonitor*)(obj)) +#define GST_VALIDATE_BIN_MONITOR_CLASS_CAST(klass) ((GstValidateBinMonitorClass*)(klass)) +#endif + +typedef struct _GstValidateBinMonitor GstValidateBinMonitor; +typedef struct _GstValidateBinMonitorClass GstValidateBinMonitorClass; + +/** + * GstValidateBinMonitor: + * + * GStreamer Validate BinMonitor class. + * + * Class that wraps a #GstBin for Validate checks + */ +struct _GstValidateBinMonitor { + GstValidateElementMonitor parent; + + GList *element_monitors; + + GstValidateScenario *scenario; + + /*< private >*/ + gulong element_added_id; + gulong element_removed_id; +}; + +/** + * GstValidateBinMonitorClass: + * @parent_class: parent + * + * GStreamer Validate BinMonitor object class. + */ +struct _GstValidateBinMonitorClass { + GstValidateElementMonitorClass parent_class; +}; + +/* normal GObject stuff */ +GST_VALIDATE_API +GType gst_validate_bin_monitor_get_type (void); + +GST_VALIDATE_API GstValidateBinMonitor * +gst_validate_bin_monitor_new (GstBin * bin, + GstValidateRunner * runner, + GstValidateMonitor * parent); + +GST_VALIDATE_API GstValidateScenario * +gst_validate_bin_monitor_get_scenario (GstValidateBinMonitor * monitor); + +G_END_DECLS + +#endif /* __GST_VALIDATE_BIN_MONITOR_H__ */ + diff --git a/validate/gst/validate/gst-validate-element-monitor.c b/validate/gst/validate/gst-validate-element-monitor.c new file mode 100644 index 0000000000..2966b14957 --- /dev/null +++ b/validate/gst/validate/gst-validate-element-monitor.c @@ -0,0 +1,352 @@ +/* GStreamer + * + * Copyright (C) 2013 Collabora Ltd. + * Author: Thiago Sousa Santos + * + * gst-validate-element-monitor.c - Validate ElementMonitor class + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gst-validate-internal.h" +#include "gst-validate-element-monitor.h" +#include "gst-validate-pad-monitor.h" +#include "gst-validate-monitor-factory.h" +#include "gst-validate-utils.h" +#include "validate.h" +#include + +/** + * SECTION:gst-validate-element-monitor + * @short_description: Class that wraps a #GstElement for Validate checks + * + * TODO + */ + +#define gst_validate_element_monitor_parent_class parent_class +G_DEFINE_TYPE (GstValidateElementMonitor, gst_validate_element_monitor, + GST_TYPE_VALIDATE_MONITOR); + +static void +gst_validate_element_monitor_wrap_pad (GstValidateElementMonitor * monitor, + GstPad * pad); +static gboolean gst_validate_element_monitor_do_setup (GstValidateMonitor * + monitor); +static GstElement *gst_validate_element_monitor_get_element (GstValidateMonitor + * monitor); + +static void +_validate_element_pad_added (GstElement * element, GstPad * pad, + GstValidateElementMonitor * monitor); + +static void +gst_validate_element_set_media_descriptor (GstValidateMonitor * monitor, + GstValidateMediaDescriptor * media_descriptor) +{ + gboolean done; + GstPad *pad; + GstValidateMonitor *pmonitor; + GstIterator *iterator; + GstElement *elem = GST_ELEMENT (gst_validate_monitor_get_target (monitor)); + + iterator = gst_element_iterate_pads (elem); + gst_object_unref (elem); + done = FALSE; + while (!done) { + GValue value = { 0, }; + + switch (gst_iterator_next (iterator, &value)) { + case GST_ITERATOR_OK: + + pad = g_value_get_object (&value); + + pmonitor = g_object_get_data ((GObject *) pad, "validate-monitor"); + if (pmonitor) + gst_validate_monitor_set_media_descriptor (pmonitor, + media_descriptor); + g_value_reset (&value); + break; + case GST_ITERATOR_RESYNC: + /* TODO how to handle this? */ + gst_iterator_resync (iterator); + break; + case GST_ITERATOR_ERROR: + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (iterator); +} + +static void +purge_and_unref_reporter (gpointer data) +{ + GstValidateReporter *reporter = data; + + gst_validate_reporter_purge_reports (reporter); + g_object_unref (reporter); +} + +static void +gst_validate_element_monitor_dispose (GObject * object) +{ + GstValidateElementMonitor *monitor = + GST_VALIDATE_ELEMENT_MONITOR_CAST (object); + GstObject *target = + gst_validate_monitor_get_target (GST_VALIDATE_MONITOR (monitor)); + + if (target) { + if (monitor->pad_added_id) + g_signal_handler_disconnect (target, monitor->pad_added_id); + gst_object_unref (target); + } + + g_list_free_full (monitor->pad_monitors, purge_and_unref_reporter); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + + +static void +gst_validate_element_monitor_class_init (GstValidateElementMonitorClass * klass) +{ + GObjectClass *gobject_class; + GstValidateMonitorClass *monitor_klass; + + gobject_class = G_OBJECT_CLASS (klass); + monitor_klass = GST_VALIDATE_MONITOR_CLASS (klass); + + gobject_class->dispose = gst_validate_element_monitor_dispose; + + monitor_klass->setup = gst_validate_element_monitor_do_setup; + monitor_klass->get_element = gst_validate_element_monitor_get_element; + monitor_klass->set_media_descriptor = + gst_validate_element_set_media_descriptor; +} + +static void +gst_validate_element_monitor_init (GstValidateElementMonitor * element_monitor) +{ +} + +/** + * gst_validate_element_monitor_new: + * @element: (transfer none): a #GstElement to run Validate on + */ +GstValidateElementMonitor * +gst_validate_element_monitor_new (GstElement * element, + GstValidateRunner * runner, GstValidateMonitor * parent) +{ + GstValidateElementMonitor *monitor; + GstElement *target; + + g_return_val_if_fail (element != NULL, NULL); + + monitor = g_object_new (GST_TYPE_VALIDATE_ELEMENT_MONITOR, "object", element, + "validate-runner", runner, "validate-parent", parent, NULL); + + target = + GST_ELEMENT (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (monitor))); + + if (!target) { + g_object_unref (monitor); + return NULL; + } + + gst_object_unref (target); + return monitor; +} + +static GstElement * +gst_validate_element_monitor_get_element (GstValidateMonitor * monitor) +{ + return GST_ELEMENT (gst_validate_monitor_get_target (monitor)); +} + +static void +gst_validate_element_monitor_inspect (GstValidateElementMonitor * monitor) +{ + GstElement *element; + GstElementClass *klass; + const gchar *klassname; + + element = + GST_ELEMENT_CAST (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (monitor))); + klass = GST_ELEMENT_CLASS (G_OBJECT_GET_CLASS (element)); + + + klassname = + gst_element_class_get_metadata (klass, GST_ELEMENT_METADATA_KLASS); + if (klassname) { + monitor->is_decoder = strstr (klassname, "Decoder") != NULL; + monitor->is_encoder = strstr (klassname, "Encoder") != NULL; + monitor->is_demuxer = strstr (klassname, "Demuxer") != NULL; + monitor->is_converter = strstr (klassname, "Converter") != NULL; + } else + GST_ERROR_OBJECT (element, "no klassname"); + + monitor->is_sink = GST_IS_BASE_SINK (element); + + gst_object_unref (element); +} + +static void +set_config_properties (GstValidateMonitor * monitor, GstElement * element) +{ + GList *config, *l; + + config = gst_validate_plugin_get_config (NULL); + for (l = config; l != NULL; l = g_list_next (l)) { + GstStructure *s = l->data; + const gchar *klass; + gchar *tmp; + const gchar *prop_name; + const GValue *prop_value; + + if (g_strcmp0 (gst_structure_get_string (s, "action"), "set-property") != 0) + continue; + + klass = gst_structure_get_string (s, "target-element-klass"); + if (klass && !gst_validate_element_has_klass (element, klass)) + continue; + + prop_name = gst_structure_get_string (s, "property-name"); + if (!prop_name + || !g_object_class_find_property (G_OBJECT_GET_CLASS (element), + prop_name)) + continue; + + prop_value = gst_structure_get_value (s, "property-value"); + if (!prop_value) + continue; + + tmp = gst_value_serialize (prop_value); + gst_validate_printf (monitor, "Setting %s to %s", prop_name, tmp); + g_free (tmp); + gst_validate_object_set_property (GST_VALIDATE_REPORTER (monitor), + G_OBJECT (element), prop_name, prop_value, FALSE); + } +} + +static gboolean +gst_validate_element_monitor_do_setup (GstValidateMonitor * monitor) +{ + GstIterator *iterator; + gboolean done; + GstPad *pad; + GstValidateElementMonitor *elem_monitor; + GstElement *element; + GstObject *target = gst_validate_monitor_get_target (monitor); + + if (!GST_IS_ELEMENT (target)) { + gst_object_unref (target); + GST_WARNING_OBJECT (monitor, "Trying to create element monitor with other " + "type of object"); + return FALSE; + } + + elem_monitor = GST_VALIDATE_ELEMENT_MONITOR_CAST (monitor); + + GST_DEBUG_OBJECT (monitor, "Setting up monitor for element %" GST_PTR_FORMAT, + target); + element = GST_ELEMENT_CAST (target); + + if (g_object_get_data ((GObject *) element, "validate-monitor")) { + GST_DEBUG_OBJECT (elem_monitor, + "Pad already has a validate-monitor associated"); + gst_object_unref (target); + return FALSE; + } + + if (!GST_IS_BIN (element)) + gst_validate_element_monitor_inspect (elem_monitor); + + elem_monitor->pad_added_id = g_signal_connect (element, "pad-added", + G_CALLBACK (_validate_element_pad_added), monitor); + + iterator = gst_element_iterate_pads (element); + done = FALSE; + while (!done) { + GValue value = { 0, }; + + switch (gst_iterator_next (iterator, &value)) { + case GST_ITERATOR_OK: + pad = g_value_get_object (&value); + gst_validate_element_monitor_wrap_pad (elem_monitor, pad); + g_value_reset (&value); + break; + case GST_ITERATOR_RESYNC: + /* TODO how to handle this? */ + gst_iterator_resync (iterator); + break; + case GST_ITERATOR_ERROR: + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (iterator); + gst_object_unref (target); + + set_config_properties (monitor, element); + + return TRUE; +} + +static void +gst_validate_element_monitor_wrap_pad (GstValidateElementMonitor * monitor, + GstPad * pad) +{ + GstValidatePadMonitor *pad_monitor; + GstValidateRunner *runner = + gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (monitor)); + + GST_DEBUG_OBJECT (monitor, "Wrapping pad %s:%s", GST_DEBUG_PAD_NAME (pad)); + + pad_monitor = + GST_VALIDATE_PAD_MONITOR (gst_validate_monitor_factory_create (GST_OBJECT + (pad), runner, GST_VALIDATE_MONITOR (monitor))); + g_return_if_fail (pad_monitor != NULL); + + GST_VALIDATE_MONITOR_LOCK (monitor); + monitor->pad_monitors = g_list_prepend (monitor->pad_monitors, pad_monitor); + GST_VALIDATE_MONITOR_UNLOCK (monitor); + + gst_object_unref (runner); +} + +static void +_validate_element_pad_added (GstElement * element, GstPad * pad, + GstValidateElementMonitor * monitor) +{ + GstObject *target = + gst_validate_monitor_get_target (GST_VALIDATE_MONITOR (monitor)); + + g_return_if_fail (target == (GstObject *) element); + gst_object_unref (target); + gst_validate_element_monitor_wrap_pad (monitor, pad); +} diff --git a/validate/gst/validate/gst-validate-element-monitor.h b/validate/gst/validate/gst-validate-element-monitor.h new file mode 100644 index 0000000000..025153d1f8 --- /dev/null +++ b/validate/gst/validate/gst-validate-element-monitor.h @@ -0,0 +1,93 @@ +/* GStreamer + * Copyright (C) 2013 Thiago Santos + * + * gst-validate-element-monitor.h - Validate ElementMonitor class + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_VALIDATE_ELEMENT_MONITOR_H__ +#define __GST_VALIDATE_ELEMENT_MONITOR_H__ + +#include +#include + +#include + +G_BEGIN_DECLS + +#ifndef __GI_SCANNER__ +#define GST_TYPE_VALIDATE_ELEMENT_MONITOR (gst_validate_element_monitor_get_type ()) +#define GST_IS_VALIDATE_ELEMENT_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_VALIDATE_ELEMENT_MONITOR)) +#define GST_IS_VALIDATE_ELEMENT_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_VALIDATE_ELEMENT_MONITOR)) +#define GST_VALIDATE_ELEMENT_MONITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_VALIDATE_ELEMENT_MONITOR, GstValidateElementMonitorClass)) +#define GST_VALIDATE_ELEMENT_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_VALIDATE_ELEMENT_MONITOR, GstValidateElementMonitor)) +#define GST_VALIDATE_ELEMENT_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_VALIDATE_ELEMENT_MONITOR, GstValidateElementMonitorClass)) +#define GST_VALIDATE_ELEMENT_MONITOR_CAST(obj) ((GstValidateElementMonitor*)(obj)) +#define GST_VALIDATE_ELEMENT_MONITOR_CLASS_CAST(klass) ((GstValidateElementMonitorClass*)(klass)) +#endif + +#define GST_VALIDATE_ELEMENT_MONITOR_ELEMENT_IS_DECODER(m) (GST_VALIDATE_ELEMENT_MONITOR_CAST (m)->is_decoder) +#define GST_VALIDATE_ELEMENT_MONITOR_ELEMENT_IS_ENCODER(m) (GST_VALIDATE_ELEMENT_MONITOR_CAST (m)->is_encoder) +#define GST_VALIDATE_ELEMENT_MONITOR_ELEMENT_IS_DEMUXER(m) (GST_VALIDATE_ELEMENT_MONITOR_CAST (m)->is_demuxer) +#define GST_VALIDATE_ELEMENT_MONITOR_ELEMENT_IS_CONVERTER(m) (GST_VALIDATE_ELEMENT_MONITOR_CAST (m)->is_converter) +#define GST_VALIDATE_ELEMENT_MONITOR_ELEMENT_IS_SINK(m) (GST_VALIDATE_ELEMENT_MONITOR_CAST (m)->is_sink) + +typedef struct _GstValidateElementMonitor GstValidateElementMonitor; +typedef struct _GstValidateElementMonitorClass GstValidateElementMonitorClass; + +/** + * GstValidateElementMonitor: + * + * GStreamer Validate ElementMonitor class. + * + * Class that wraps a #GstElement for Validate checks + */ +struct _GstValidateElementMonitor { + GstValidateMonitor parent; + + /*< private >*/ + gulong pad_added_id; + GList *pad_monitors; + + gboolean is_decoder; + gboolean is_encoder; + gboolean is_demuxer; + gboolean is_converter; + gboolean is_sink; +}; + +/** + * GstValidateElementMonitorClass: + * @parent_class: parent + * + * GStreamer Validate ElementMonitor object class. + */ +struct _GstValidateElementMonitorClass { + GstValidateMonitorClass parent_class; +}; + +/* normal GObject stuff */ +GST_VALIDATE_API +GType gst_validate_element_monitor_get_type (void); + +GST_VALIDATE_API +GstValidateElementMonitor * gst_validate_element_monitor_new (GstElement * element, GstValidateRunner * runner, GstValidateMonitor * parent); + +G_END_DECLS + +#endif /* __GST_VALIDATE_ELEMENT_MONITOR_H__ */ + diff --git a/validate/gst/validate/gst-validate-enum-types.c.template b/validate/gst/validate/gst-validate-enum-types.c.template new file mode 100644 index 0000000000..ee4787b09b --- /dev/null +++ b/validate/gst/validate/gst-validate-enum-types.c.template @@ -0,0 +1,41 @@ +/*** BEGIN file-header ***/ +#include "gst-validate-enum-types.h" +#include +#define C_ENUM(v) ((gint) v) +#define C_FLAGS(v) ((guint) v) + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* enumerations from "@basename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType +@enum_name@_get_type (void) +{ + static gsize id = 0; + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { C_@TYPE@(@VALUENAME@), "@VALUENAME@", "@valuenick@" }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + + if (g_once_init_enter (&id)) { + GType tmp = g_@type@_register_static ("@EnumName@", values); + g_once_init_leave (&id, tmp); + } + + return (GType) id; +} + +/*** END value-tail ***/ + +/*** BEGIN file-tail ***/ + +/*** END file-tail ***/ diff --git a/validate/gst/validate/gst-validate-enum-types.h.template b/validate/gst/validate/gst-validate-enum-types.h.template new file mode 100644 index 0000000000..08a80cbf8a --- /dev/null +++ b/validate/gst/validate/gst-validate-enum-types.h.template @@ -0,0 +1,26 @@ +/*** BEGIN file-header ***/ +#ifndef __GST_ENUM_TYPES_H__ +#define __GST_ENUM_TYPES_H__ + +#include +#include +#include + +G_BEGIN_DECLS +/*** END file-header ***/ + +/*** BEGIN file-production ***/ + +/* enumerations from "@basename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GST_VALIDATE_API GType @enum_name@_get_type (void); +#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ()) +/*** END value-header ***/ + +/*** BEGIN file-tail ***/ +G_END_DECLS + +#endif /* __GST_ENUM_TYPES_H__ */ +/*** END file-tail ***/ diff --git a/validate/gst/validate/gst-validate-enums.h b/validate/gst/validate/gst-validate-enums.h new file mode 100644 index 0000000000..3dbf994818 --- /dev/null +++ b/validate/gst/validate/gst-validate-enums.h @@ -0,0 +1,116 @@ +/* GStreamer + * Copyright (C) 2014 Mathieu Duponchelle + * + * gst-validate-enums.h - Validate constants. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_VALIDATE_ENUMS_H__ +#define __GST_VALIDATE_ENUMS_H__ + +/** + * SECTION: gst-validate-enums.h + * @title: GstValidate enums + */ + +/** + * GstValidateReportingDetails: + * @GST_VALIDATE_SHOW_NONE: No debugging level specified or desired. Used to deactivate + * debugging output. + * @GST_VALIDATE_SHOW_SMART: Sythetic for not fatal issues and detailed for + * others + * @GST_VALIDATE_SHOW_SYNTHETIC: Summary of the issues found, with no + * details. + * @GST_VALIDATE_SHOW_SUBCHAIN: If set as the default level, similar + * issues can be reported multiple times for different subchains. + * If set as the level for a particular object (my_object:subchain), validate + * will report the issues where the object is the first to report an issue for + * a subchain. + * @GST_VALIDATE_SHOW_MONITOR: If set as the default level, all the + * distinct issues for all the monitors will be reported. + * If set as the level for a particular object, all the distinct issues for this object + * will be reported. + * Note that if the same issue happens twice on the same object, up until this + * level that issue is only reported once. + * @GST_VALIDATE_SHOW_ALL: All the issues will be reported, even those + * that repeat themselves inside the same object. This can be *very* verbose if + * set globally. + * @GST_VALIDATE_SHOW_UNKNOWN: No reporting level known, + * reporting will default to the global reporting level. + * + * Setting the reporting level allows to control the way issues are reported + * when calling #gst_validate_runner_printf. + * + * The reporting level can be set through the "GST_VALIDATE_REPORTING_DETAILS" + * environment variable, as a comma-separated list of (optional) object categories / names + * and levels. No object category / name sets the global level. + * + * Examples: GST_VALIDATE_REPORTING_DETAILS=synthetic,h264parse:all + * GST_VALIDATE_REPORTING_DETAILS=none,h264parse::sink_0:synthetic + */ +typedef enum { + GST_VALIDATE_SHOW_UNKNOWN = 0, + GST_VALIDATE_SHOW_NONE = 1, + GST_VALIDATE_SHOW_SYNTHETIC = 2, + GST_VALIDATE_SHOW_SUBCHAIN = 3, + GST_VALIDATE_SHOW_MONITOR = 4, + GST_VALIDATE_SHOW_ALL = 5, + GST_VALIDATE_SHOW_SMART = 6, + GST_VALIDATE_SHOW_COUNT +} GstValidateReportingDetails; + +/** + * GST_VALIDATE_SHOW_DEFAULT: + * + * Defines the default reporting level to be used with gst-validate. It is normally + * set to #GST_VALIDATE_SHOW_SYNTHETIC so only a synthetic report + * gets printed. + * As it can be configured at compile time, developer builds may chose to + * override that though. + */ +#ifndef GST_VALIDATE_SHOW_DEFAULT +#define GST_VALIDATE_SHOW_DEFAULT GST_VALIDATE_SHOW_SMART +#endif + +/** + * GstValidateVerbosityFlags: + * + * Defines the level of verbosity of -validate (ie, printing on stdout). + */ +typedef enum +{ + GST_VALIDATE_VERBOSITY_NONE = 0, + GST_VALIDATE_VERBOSITY_POSITION = 1 << 1, + GST_VALIDATE_VERBOSITY_MESSAGES = 1 << 2, + GST_VALIDATE_VERBOSITY_PROPS_CHANGES = 1 << 3, + GST_VALIDATE_VERBOSITY_NEW_ELEMENTS = 1 << 4, + GST_VALIDATE_VERBOSITY_ALL = GST_VALIDATE_VERBOSITY_POSITION | GST_VALIDATE_VERBOSITY_MESSAGES | GST_VALIDATE_VERBOSITY_PROPS_CHANGES | GST_VALIDATE_VERBOSITY_NEW_ELEMENTS +} GstValidateVerbosityFlags; + +/** + * GstValidateStructureResolveVariablesFlags: + * + * Since: 1.20 + */ +typedef enum { + GST_VALIDATE_STRUCTURE_RESOLVE_VARIABLES_ALL = 0, + GST_VALIDATE_STRUCTURE_RESOLVE_VARIABLES_LOCAL_ONLY = 1 << 0, + GST_VALIDATE_STRUCTURE_RESOLVE_VARIABLES_NO_FAILURE = 1 << 1, + GST_VALIDATE_STRUCTURE_RESOLVE_VARIABLES_NO_EXPRESSION = 1 << 1, +} GstValidateStructureResolveVariablesFlags; + +#endif /* __GST_VALIDATE_ENUMS_H__ */ diff --git a/validate/gst/validate/gst-validate-extra-checks.c b/validate/gst/validate/gst-validate-extra-checks.c new file mode 100644 index 0000000000..01b82fe093 --- /dev/null +++ b/validate/gst/validate/gst-validate-extra-checks.c @@ -0,0 +1,166 @@ +#include +#include "validate.h" +#include "gst-validate-utils.h" +#include "gst-validate-internal.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define EXTRA_CHECKS_WRONG_NUMBER_OF_INSTANCES g_quark_from_static_string ("extrachecks::wrong-number-of-instances") + +typedef struct +{ + gchar *pname; + gchar *klass; + gint expected_n_instances; + gint n_instances; +} CheckNumInstanceData; + +static CheckNumInstanceData * +gst_validate_check_num_instances_data_new (GstStructure * check) +{ + CheckNumInstanceData *data = g_new0 (CheckNumInstanceData, 1); + + if (!gst_structure_get_int (check, "num-instances", + &data->expected_n_instances)) { + gst_validate_abort + ("[CONFIG ERROR] Mandatory field `num-instances` not found in " + "extra-check `num-instances`"); + goto failed; + } + + data->pname = g_strdup (gst_structure_get_string (check, "pipeline-name")); + if (!data->pname) { + gst_validate_abort + ("[CONFIG ERROR] Mandatory field `pipeline` not found in " + "extra-check `num-instances`"); + goto failed; + } + + data->klass = g_strdup (gst_structure_get_string (check, "element-klass")); + if (!data->klass) { + gst_validate_abort + ("[CONFIG ERROR] Mandatory field `element-klass` not found in " + "extra-check `num-instances`"); + goto failed; + } + + return data; + +failed: + g_free (data); + g_free (data->klass); + + return NULL; +} + +static void +gst_validate_check_num_instances_data_free (CheckNumInstanceData * data) +{ + g_free (data->pname); + g_free (data); +} + +static void +gst_validate_check_num_instances (GstValidateOverride * o, + GstValidateMonitor * monitor, GstElement * nelem) +{ + gchar *pname; + CheckNumInstanceData *data = g_object_get_data (G_OBJECT (o), "check-data"); + GstPipeline *pipe = gst_validate_monitor_get_pipeline (monitor); + + if (!pipe) + return; + + pname = gst_object_get_name (GST_OBJECT (pipe)); + if (g_strcmp0 (data->pname, pname)) + goto done; + + if (!gst_validate_element_has_klass (nelem, data->klass)) + return; + + data->n_instances++; + + if (data->n_instances > data->expected_n_instances) { + GST_VALIDATE_REPORT (o, EXTRA_CHECKS_WRONG_NUMBER_OF_INSTANCES, + "%d instances allows in pipeline %s but already %d where added.", + data->expected_n_instances, pname, data->n_instances); + } + GST_ERROR_OBJECT (nelem, "HERE I AM %d", data->n_instances); + +done: + g_free (pname); + gst_object_unref (pipe); +} + +static void +runner_stopping (GstValidateRunner * runner, GstValidateOverride * o) +{ + CheckNumInstanceData *data = g_object_get_data (G_OBJECT (o), "check-data"); + + if (data->expected_n_instances != data->n_instances) { + GST_VALIDATE_REPORT (o, EXTRA_CHECKS_WRONG_NUMBER_OF_INSTANCES, + "%d instances expected in pipeline %s but %d where added.", + data->expected_n_instances, data->pname, data->n_instances); + } +} + +static void +_runner_set (GObject * object, GParamSpec * pspec, gpointer user_data) +{ + GstValidateRunner *runner = + gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (object)); + + g_signal_connect (runner, "stopping", G_CALLBACK (runner_stopping), object); + gst_object_unref (runner); +} + +static void +gst_validate_add_num_instances_check (GstStructure * structure) +{ + CheckNumInstanceData *data = + gst_validate_check_num_instances_data_new (structure); + GstValidateOverride *o = gst_validate_override_new (); + + g_object_set_data_full (G_OBJECT (o), "check-data", data, + (GDestroyNotify) gst_validate_check_num_instances_data_free); + + gst_validate_override_set_element_added_handler (o, + gst_validate_check_num_instances); + + g_signal_connect (o, "notify::validate-runner", G_CALLBACK (_runner_set), + NULL); + + gst_validate_override_register_by_type (GST_TYPE_BIN, o); + gst_object_unref (o); +} + +gboolean +gst_validate_extra_checks_init () +{ + GList *config, *tmp; + config = gst_validate_get_config ("extrachecks"); + + if (!config) + return TRUE; + + for (tmp = config; tmp; tmp = tmp->next) { + GstStructure *check = tmp->data; + + if (gst_structure_has_field (check, "num-instances")) + gst_validate_add_num_instances_check (check); + } + g_list_free (config); + + gst_validate_issue_register (gst_validate_issue_new + (EXTRA_CHECKS_WRONG_NUMBER_OF_INSTANCES, + "The configured number of possible instances of an element type" + " in a pipeline is not respected.", + "The `num-instances` extra checks allow user to make sure that" + " a previously defined number of instances of an element is added" + " in a given pipeline, that test failed.", + GST_VALIDATE_REPORT_LEVEL_CRITICAL)); + + return TRUE; +} diff --git a/validate/gst/validate/gst-validate-i18n-lib.h b/validate/gst/validate/gst-validate-i18n-lib.h new file mode 100644 index 0000000000..f01f677d83 --- /dev/null +++ b/validate/gst/validate/gst-validate-i18n-lib.h @@ -0,0 +1,47 @@ +/* GStreamer + * Copyright (C) 2004 Thomas Vander Stichele + * + * gst-validate-i18n-lib.h: internationalization macros for the GStreamer libraries + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef __GST_VALIDATE_I18N_LIB_H__ +#define __GST_VALIDATE_I18N_LIB_H__ + +#ifndef PACKAGE_NAME +#error You must include config.h before including this header. +#endif + +#ifdef ENABLE_NLS + +#include /* some people need it and some people don't */ +#include "gettext.h" /* included with gettext distribution and copied */ + +/* we want to use shorthand _() for translating and N_() for marking */ +#define _(String) dgettext (GETTEXT_PACKAGE, String) +#define N_(String) gettext_noop (String) +/* FIXME: if we need it, we can add Q_ as well, like in glib */ + +#else +#define _(String) String +#define N_(String) String +#define ngettext(Singular,Plural,Count) ((Count>1)?Plural:Singular) + +#endif + +#endif /* __GST_VALIDATE_I18N_LIB_H__ */ diff --git a/validate/gst/validate/gst-validate-internal.h b/validate/gst/validate/gst-validate-internal.h new file mode 100644 index 0000000000..74b14e2bdc --- /dev/null +++ b/validate/gst/validate/gst-validate-internal.h @@ -0,0 +1,74 @@ +/* GStreamer + * Copyright (C) 2013 Thiago Santos + * + * validate.c - Validate generic functions + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_VALIDATE_INTERNAL_H__ +#define __GST_VALIDATE_INTERNAL_H__ + +#include +#include "gst-validate-scenario.h" +#include "gst-validate-monitor.h" +#include + +extern G_GNUC_INTERNAL GstDebugCategory *gstvalidate_debug; +#define GST_CAT_DEFAULT gstvalidate_debug + +extern G_GNUC_INTERNAL GRegex *newline_regex; +extern G_GNUC_INTERNAL GstClockTime _priv_start_time; + +extern G_GNUC_INTERNAL GQuark _Q_VALIDATE_MONITOR; + +/* If an action type is 1 (TRUE) we also consider it is a config to keep backward compatibility */ +#define IS_CONFIG_ACTION_TYPE(type) (((type) & GST_VALIDATE_ACTION_TYPE_CONFIG) || ((type) == TRUE)) + +extern G_GNUC_INTERNAL GType _gst_validate_action_type_type; + +void init_scenarios (void); +void register_action_types (void); + +/* FIXME 2.0 Remove that as this is only for backward compatibility + * as we used to have to print actions in the action execution function + * and this is done by the scenario itself now */ +G_GNUC_INTERNAL gboolean _action_check_and_set_printed (GstValidateAction *action); +G_GNUC_INTERNAL gboolean gst_validate_action_get_level (GstValidateAction *action); +G_GNUC_INTERNAL gboolean gst_validate_scenario_check_and_set_needs_clock_sync (GList *structures, GstStructure **meta); + +#define GST_VALIDATE_SCENARIO_SUFFIX ".scenario" +G_GNUC_INTERNAL gchar** gst_validate_scenario_get_include_paths(const gchar* relative_scenario); +G_GNUC_INTERNAL void _priv_validate_override_registry_deinit(void); + +G_GNUC_INTERNAL GstValidateReportingDetails gst_validate_runner_get_default_reporting_details (GstValidateRunner *runner); + +G_GNUC_INTERNAL GstValidateMonitor * gst_validate_get_monitor (GObject *object); +G_GNUC_INTERNAL void gst_validate_init_runner (void); +G_GNUC_INTERNAL void gst_validate_deinit_runner (void); +G_GNUC_INTERNAL void gst_validate_report_deinit (void); +G_GNUC_INTERNAL gboolean gst_validate_send (JsonNode * root); +G_GNUC_INTERNAL void gst_validate_set_test_file_globals (GstStructure* meta, const gchar* testfile, gboolean use_fakesinks); +G_GNUC_INTERNAL gboolean gst_validate_get_test_file_scenario (GList** structs, const gchar** scenario_name, gchar** original_name); +G_GNUC_INTERNAL GstValidateScenario* gst_validate_scenario_from_structs (GstValidateRunner* runner, GstElement* pipeline, GList* structures, + gchar* origin_file); +G_GNUC_INTERNAL GList* gst_validate_get_config (const gchar *structname); +G_GNUC_INTERNAL GList * gst_validate_get_test_file_expected_issues (void); + +G_GNUC_INTERNAL gboolean gst_validate_extra_checks_init (void); +G_GNUC_INTERNAL gboolean gst_validate_flow_init (void); +G_GNUC_INTERNAL gboolean is_tty (void); +#endif diff --git a/validate/gst/validate/gst-validate-media-info.c b/validate/gst/validate/gst-validate-media-info.c new file mode 100644 index 0000000000..ed1113f4f5 --- /dev/null +++ b/validate/gst/validate/gst-validate-media-info.c @@ -0,0 +1,1127 @@ +/* GStreamer + * + * Copyright (C) 2013 Collabora Ltd. + * Author: Thiago Sousa Santos + * + * gst-validate-media-info.c + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gst-validate-media-info.h" +#include "validate.h" + +#include +#include + +struct _GstValidateStreamInfo +{ + GstCaps *caps; + + GList *children; +}; + +static GstValidateStreamInfo * +gst_validate_stream_info_from_discoverer_info (GstDiscovererStreamInfo * info) +{ + GstValidateStreamInfo *ret = g_new0 (GstValidateStreamInfo, 1); + + ret->caps = gst_discoverer_stream_info_get_caps (info); + if (GST_IS_DISCOVERER_CONTAINER_INFO (info)) { + GList *streams = + gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO + (info)); + GList *iter; + + for (iter = streams; iter; iter = g_list_next (iter)) { + ret->children = g_list_append (ret->children, + gst_validate_stream_info_from_discoverer_info (iter->data)); + } + gst_discoverer_stream_info_list_free (streams); + } + + return ret; +} + +static GstValidateStreamInfo * +gst_validate_stream_info_from_caps_string (gchar * capsstr) +{ + GstValidateStreamInfo *ret = g_new0 (GstValidateStreamInfo, 1); + + ret->caps = gst_caps_from_string (capsstr); + + return ret; +} + +static void +gst_validate_stream_info_free (GstValidateStreamInfo * si) +{ + if (si->caps) + gst_caps_unref (si->caps); + g_list_free_full (si->children, + (GDestroyNotify) gst_validate_stream_info_free); + g_free (si); +} + +void +gst_validate_media_info_init (GstValidateMediaInfo * mi) +{ + mi->uri = NULL; + mi->file_size = 0; + mi->duration = GST_CLOCK_TIME_NONE; + mi->seekable = FALSE; + mi->stream_info = NULL; + mi->playback_error = NULL; + mi->reverse_playback_error = NULL; + mi->track_switch_error = NULL; + mi->is_image = FALSE; + mi->discover_only = FALSE; +} + +void +gst_validate_media_info_clear (GstValidateMediaInfo * mi) +{ + g_free (mi->uri); + g_free (mi->playback_error); + g_free (mi->reverse_playback_error); + g_free (mi->track_switch_error); + if (mi->stream_info) + gst_validate_stream_info_free (mi->stream_info); +} + +void +gst_validate_media_info_free (GstValidateMediaInfo * mi) +{ + gst_validate_media_info_clear (mi); + g_free (mi); +} + +gchar * +gst_validate_media_info_to_string (GstValidateMediaInfo * mi, gsize * length) +{ + GKeyFile *kf = g_key_file_new (); + gchar *data = NULL; + gchar *str; + + /* file info */ + g_key_file_set_string (kf, "file-info", "uri", mi->uri); + g_key_file_set_uint64 (kf, "file-info", "file-size", mi->file_size); + + /* media info */ + g_key_file_set_uint64 (kf, "media-info", "file-duration", mi->duration); + g_key_file_set_boolean (kf, "media-info", "seekable", mi->seekable); + g_key_file_set_boolean (kf, "media-info", "is-image", mi->is_image); + + if (mi->stream_info && mi->stream_info->caps) { + str = gst_caps_to_string (mi->stream_info->caps); + g_key_file_set_string (kf, "media-info", "caps", str); + g_free (str); + } + + /* playback tests */ + g_key_file_set_string (kf, "playback-tests", "playback-error", + mi->playback_error ? mi->playback_error : ""); + g_key_file_set_string (kf, "playback-tests", "reverse-playback-error", + mi->reverse_playback_error ? mi->reverse_playback_error : ""); + g_key_file_set_string (kf, "playback-tests", "track-switch-error", + mi->track_switch_error ? mi->track_switch_error : ""); + + data = g_key_file_to_data (kf, length, NULL); + g_key_file_free (kf); + + return data; +} + +gboolean +gst_validate_media_info_save (GstValidateMediaInfo * mi, const gchar * path, + GError ** err) +{ + gchar *data = NULL; + gsize datalength = 0; + + data = gst_validate_media_info_to_string (mi, &datalength); + + if (!g_file_set_contents (path, data, datalength, err)) + return FALSE; + return TRUE; +} + +/** + * gst_validate_media_info_load: (skip): + */ +GstValidateMediaInfo * +gst_validate_media_info_load (const gchar * path, GError ** err) +{ + GKeyFile *kf = g_key_file_new (); + GstValidateMediaInfo *mi; + gchar *str; + + if (!g_key_file_load_from_file (kf, path, G_KEY_FILE_NONE, err)) { + g_key_file_free (kf); + return NULL; + } + + mi = g_new (GstValidateMediaInfo, 1); + gst_validate_media_info_init (mi); + + mi->uri = g_key_file_get_string (kf, "file-info", "uri", err); + if (err && *err) + goto end; + mi->file_size = g_key_file_get_uint64 (kf, "file-info", "file-size", err); + if (err && *err) + goto end; + + mi->duration = + g_key_file_get_uint64 (kf, "media-info", "file-duration", NULL); + mi->seekable = g_key_file_get_boolean (kf, "media-info", "seekable", NULL); + mi->is_image = g_key_file_get_boolean (kf, "media-info", "is-image", NULL); + + str = g_key_file_get_string (kf, "media-info", "caps", NULL); + if (str) { + mi->stream_info = gst_validate_stream_info_from_caps_string (str); + g_free (str); + } + + mi->playback_error = + g_key_file_get_string (kf, "playback-tests", "playback-error", NULL); + mi->reverse_playback_error = + g_key_file_get_string (kf, "playback-tests", "reverse-playback-error", + NULL); + mi->track_switch_error = + g_key_file_get_string (kf, "playback-tests", "track-switch-error", NULL); + if (mi->playback_error && strlen (mi->playback_error) == 0) { + g_free (mi->playback_error); + mi->playback_error = NULL; + } + if (mi->reverse_playback_error && strlen (mi->reverse_playback_error) == 0) { + g_free (mi->reverse_playback_error); + mi->reverse_playback_error = NULL; + } + if (mi->track_switch_error && strlen (mi->track_switch_error) == 0) { + g_free (mi->track_switch_error); + mi->track_switch_error = NULL; + } + +end: + g_key_file_free (kf); + return mi; +} + +static gboolean +check_file_size (GstValidateMediaInfo * mi) +{ + GStatBuf statbuf; + gchar *filepath; + guint64 size = 0; + gboolean ret = TRUE; + GError *err = NULL; + + filepath = g_filename_from_uri (mi->uri, NULL, &err); + if (!filepath) { + g_error_free (err); + return FALSE; + } + + if (g_stat (filepath, &statbuf) == 0) { + size = statbuf.st_size; + } else { + ret = FALSE; + goto end; + } + + mi->file_size = size; + +end: + g_free (filepath); + return ret; +} + +static gboolean +check_file_duration (GstValidateMediaInfo * mi, GstDiscovererInfo * info) +{ + mi->duration = gst_discoverer_info_get_duration (info); + return TRUE; +} + +static gboolean +check_seekable (GstValidateMediaInfo * mi, GstDiscovererInfo * info) +{ + mi->seekable = gst_discoverer_info_get_seekable (info); + return TRUE; +} + +#if 0 +static inline gboolean +_gst_caps_can_intersect_safe (const GstCaps * a, const GstCaps * b) +{ + if (a == b) + return TRUE; + if ((a == NULL) || (b == NULL)) + return FALSE; + return gst_caps_can_intersect (a, b); +} + +#if 0 +typedef struct +{ + GstEncodingProfile *profile; + gint count; +} ExpectedStream; + +#define SET_MESSAGE(placeholder, msg) \ +G_STMT_START { \ + if (placeholder) { \ + *placeholder = msg; \ + } \ +} G_STMT_END + +static gboolean +compare_encoding_profile_with_discoverer_stream (GstValidateFileChecker * fc, + GstEncodingProfile * prof, GstDiscovererStreamInfo * stream, gchar ** msg); + +static gboolean + compare_container_profile_with_container_discoverer_stream + (GstValidateFileChecker * fc, GstEncodingContainerProfile * prof, + GstDiscovererContainerInfo * stream, gchar ** msg) +{ + ExpectedStream *expected_streams = NULL; + GList *container_streams; + const GList *profile_iter; + const GList *streams_iter; + gint i; + gint expected_count = g_list_length ((GList *) + gst_encoding_container_profile_get_profiles (prof)); + gboolean ret = TRUE; + + container_streams = gst_discoverer_container_info_get_streams (stream); + + if (expected_count == 0) { + if (g_list_length (container_streams) != 0) { + SET_MESSAGE (msg, + g_strdup_printf + ("No streams expected on this container, but found %u", + g_list_length (container_streams))); + ret = FALSE; + goto end; + } + } + + /* initialize expected streams data */ + expected_streams = g_malloc0 (sizeof (ExpectedStream) * expected_count); + for (i = 0, profile_iter = gst_encoding_container_profile_get_profiles (prof); + profile_iter; profile_iter = g_list_next (profile_iter), i++) { + GstEncodingProfile *prof = profile_iter->data; + ExpectedStream *expected = &(expected_streams[i]); + + expected->profile = prof; + } + + /* look for the streams on discoverer info */ + for (streams_iter = container_streams; streams_iter; + streams_iter = g_list_next (streams_iter)) { + GstDiscovererStreamInfo *info = streams_iter->data; + gboolean found = FALSE; + for (i = 0; i < expected_count; i++) { + ExpectedStream *expected = &(expected_streams[i]); + + if (compare_encoding_profile_with_discoverer_stream (fc, + expected->profile, info, NULL)) { + found = TRUE; + break; + } + } + + if (!found) { + GstCaps *caps = gst_discoverer_stream_info_get_caps (info); + gchar *caps_str = gst_caps_to_string (caps); + SET_MESSAGE (msg, + g_strdup_printf ("Stream with caps '%s' wasn't found on file", + caps_str)); + g_free (caps_str); + gst_caps_unref (caps); + ret = FALSE; + goto end; + } + } + + /* check if all expected streams are present */ + for (i = 0; i < expected_count; i++) { + ExpectedStream *expected = &(expected_streams[i]); + guint presence = gst_encoding_profile_get_presence (expected->profile); + + if (presence == 0) + continue; + + if (presence != expected->count) { + gchar *caps_str = + gst_caps_to_string (gst_encoding_profile_get_format + (expected->profile)); + SET_MESSAGE (msg, + g_strdup_printf ("Stream from profile %s (with caps '%s" + "' has presence %u but the number of streams found was %d", + gst_encoding_profile_get_name (expected->profile), caps_str, + presence, expected->count)); + g_free (caps_str); + ret = FALSE; + goto end; + } + } + +end: + g_free (expected_streams); + gst_discoverer_stream_info_list_free (container_streams); + return ret; +} + +static gboolean +compare_encoding_profile_with_discoverer_stream (GstValidateFileChecker * fc, + GstEncodingProfile * prof, GstDiscovererStreamInfo * stream, gchar ** msg) +{ + gboolean ret = TRUE; + GstCaps *caps = NULL; + const GstCaps *profile_caps; + const GstCaps *restriction_caps; + + caps = gst_discoverer_stream_info_get_caps (stream); + profile_caps = gst_encoding_profile_get_format (prof); + restriction_caps = gst_encoding_profile_get_restriction (prof); + + /* TODO need to consider profile caps restrictions */ + if (!_gst_caps_can_intersect_safe (caps, profile_caps)) { + gchar *caps_str = gst_caps_to_string (caps); + gchar *profile_caps_str = gst_caps_to_string (profile_caps); + SET_MESSAGE (msg, g_strdup_printf ("Caps '%s' didn't match profile '%s'", + profile_caps_str, caps_str)); + g_free (caps_str); + g_free (profile_caps_str); + ret = FALSE; + goto end; + } + + if (restriction_caps) { + GstStructure *structure; + gint i; + gboolean found = FALSE; + + for (i = 0; i < gst_caps_get_size (restriction_caps); i++) { + structure = gst_caps_get_structure (restriction_caps, i); + structure = gst_structure_copy (structure); + gst_structure_set_name (structure, + gst_structure_get_name (gst_caps_get_structure (caps, 0))); + if (gst_structure_can_intersect (structure, gst_caps_get_structure (caps, + 0))) { + gst_structure_free (structure); + found = TRUE; + break; + } + gst_structure_free (structure); + } + if (!found) { + gchar *caps_str = gst_caps_to_string (caps); + gchar *restriction_caps_str = gst_caps_to_string (restriction_caps); + SET_MESSAGE (msg, + g_strdup_printf ("Caps restriction '%s' wasn't respected on file " + "with caps '%s'", restriction_caps_str, caps_str)); + g_free (caps_str); + g_free (restriction_caps_str); + ret = FALSE; + goto end; + } + } + + if (GST_IS_ENCODING_CONTAINER_PROFILE (prof)) { + if (GST_IS_DISCOVERER_CONTAINER_INFO (stream)) { + ret = + ret & compare_container_profile_with_container_discoverer_stream (fc, + (GstEncodingContainerProfile *) prof, + (GstDiscovererContainerInfo *) stream, msg); + } else { + SET_MESSAGE (msg, + g_strdup_printf ("Expected container profile but found stream of %s", + gst_discoverer_stream_info_get_stream_type_nick (stream))); + ret = FALSE; + goto end; + } + + } else if (GST_IS_ENCODING_VIDEO_PROFILE (prof)) { + if (!GST_IS_DISCOVERER_VIDEO_INFO (stream)) { + SET_MESSAGE (msg, + g_strdup_printf ("Expected video profile but found stream of %s", + gst_discoverer_stream_info_get_stream_type_nick (stream))); + ret = FALSE; + goto end; + } + + } else if (GST_IS_ENCODING_AUDIO_PROFILE (prof)) { + if (!GST_IS_DISCOVERER_AUDIO_INFO (stream)) { + SET_MESSAGE (msg, + g_strdup_printf ("Expected audio profile but found stream of %s", + gst_discoverer_stream_info_get_stream_type_nick (stream))); + ret = FALSE; + goto end; + } + } else { + g_assert_not_reached (); + return FALSE; + } + + +end: + if (caps) + gst_caps_unref (caps); + + return ret; +} +#endif +#endif + +static gboolean +check_encoding_profile (GstValidateMediaInfo * mi, GstDiscovererInfo * info) +{ + gboolean ret = TRUE; + GstDiscovererStreamInfo *streaminfo; + + streaminfo = gst_discoverer_info_get_stream_info (info); + mi->stream_info = gst_validate_stream_info_from_discoverer_info (streaminfo); + + gst_discoverer_info_unref (streaminfo); + + return ret; +} + +typedef gboolean (*GstElementConfigureFunc) (GstValidateMediaInfo *, + GstElement *, gchar ** msg); +static gboolean +check_playback_scenario (GstValidateMediaInfo * mi, + GstElementConfigureFunc configure_function, gchar ** error_message) +{ + GstElement *playbin; + GstElement *videosink, *audiosink; + GstBus *bus; + GstMessage *msg; + gboolean ret = TRUE; + GstStateChangeReturn state_ret; + + playbin = gst_element_factory_make ("playbin", "fc-playbin"); + videosink = gst_element_factory_make ("fakesink", "fc-videosink"); + audiosink = gst_element_factory_make ("fakesink", "fc-audiosink"); + + if (!playbin || !videosink || !audiosink) { + *error_message = g_strdup ("Playbin and/or fakesink not available"); + } + + g_object_set (playbin, "video-sink", videosink, "audio-sink", audiosink, + "uri", mi->uri, NULL); + + bus = gst_pipeline_get_bus (GST_PIPELINE (playbin)); + + state_ret = gst_element_set_state (playbin, GST_STATE_PAUSED); + if (state_ret == GST_STATE_CHANGE_FAILURE) { + *error_message = g_strdup ("Failed to change pipeline to paused"); + ret = FALSE; + goto end; + } else if (state_ret == GST_STATE_CHANGE_ASYNC) { + msg = + gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, + GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_EOS | GST_MESSAGE_ERROR); + if (msg && GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ASYNC_DONE) { + gst_message_unref (msg); + } else { + ret = FALSE; + *error_message = g_strdup ("Playback finihshed unexpectedly"); + goto end; + } + } + + if (configure_function) { + if (!configure_function (mi, playbin, error_message)) { + gst_object_unref (bus); + gst_object_unref (playbin); + return FALSE; + } + } + + if (gst_element_set_state (playbin, + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { + *error_message = g_strdup ("Failed to set pipeline to playing"); + ret = FALSE; + goto end; + } + + msg = + gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, + GST_MESSAGE_ERROR | GST_MESSAGE_EOS); + if (msg) { + if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_EOS) { + /* all good */ + ret = TRUE; + } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) { + GError *error = NULL; + gchar *debug = NULL; + + gst_message_parse_error (msg, &error, &debug); + *error_message = g_strdup_printf ("Playback error: %s : %s", + error->message, debug); + g_error_free (error); + g_free (debug); + + ret = FALSE; + } else { + g_assert_not_reached (); + } + gst_message_unref (msg); + } else { + ret = FALSE; + *error_message = g_strdup ("Playback finihshed unexpectedly"); + } + +end: + gst_object_unref (bus); + gst_element_set_state (playbin, GST_STATE_NULL); + gst_object_unref (playbin); + + return ret; +} + +static gboolean +check_playback (GstValidateMediaInfo * mi, gchar ** msg) +{ + return check_playback_scenario (mi, NULL, msg); +} + +static gboolean +send_reverse_seek (GstValidateMediaInfo * mi, GstElement * pipeline, + gchar ** msg) +{ + gboolean ret; + + ret = gst_element_seek (pipeline, -1.0, GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, -1); + + if (!ret) { + *msg = g_strdup ("Reverse playback seek failed"); + } + return ret; +} + +static gboolean +check_reverse_playback (GstValidateMediaInfo * mi, gchar ** msg) +{ + return check_playback_scenario (mi, send_reverse_seek, msg); +} + +typedef struct +{ + guint counter; + guint back_counter; + gulong probe_id; + GstPad *pad; +} BufferCountData; + +static GstPadProbeReturn +input_selector_pad_probe (GstPad * pad, GstPadProbeInfo * info, + gpointer userdata) +{ + GstPad *sink_pad = NULL; + + if (info->type == GST_PAD_PROBE_TYPE_BUFFER) { + BufferCountData *bcd = + g_object_get_data (G_OBJECT (pad), "buffer-count-data"); + if (!bcd) { + GST_ERROR_OBJECT (pad, "No buffer-count-data found"); + return GST_PAD_PROBE_OK; + } + + ++bcd->counter; + if (GST_PAD_IS_SRC (pad)) { + g_object_get (GST_PAD_PARENT (pad), "active-pad", &sink_pad, NULL); + if (sink_pad) { + bcd = g_object_get_data (G_OBJECT (sink_pad), "buffer-count-data"); + if (!bcd) { + gst_object_unref (sink_pad); + GST_ERROR_OBJECT (pad, "No buffer-count-data found"); + return GST_PAD_PROBE_OK; + } + ++bcd->back_counter; + gst_object_unref (sink_pad); + } + } + } + return GST_PAD_PROBE_OK; +} + +static void +setup_input_selector_counters (GstElement * element) +{ + GstIterator *iterator; + gboolean done = FALSE; + GValue value = { 0, }; + GstPad *pad; + BufferCountData *bcd; + + iterator = gst_element_iterate_pads (element); + while (!done) { + switch (gst_iterator_next (iterator, &value)) { + case GST_ITERATOR_OK: + pad = g_value_dup_object (&value); + bcd = g_slice_new0 (BufferCountData); + g_object_set_data (G_OBJECT (pad), "buffer-count-data", bcd); + bcd->probe_id = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER, + (GstPadProbeCallback) input_selector_pad_probe, NULL, NULL); + bcd->pad = pad; + g_value_reset (&value); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iterator); + break; + case GST_ITERATOR_ERROR: + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (iterator); +} + +static gboolean +check_and_remove_input_selector_counters (GstElement * element, + gchar ** error_message) +{ + GstIterator *iterator; + gboolean done = FALSE; + GstPad *pad; + GValue value = { 0, }; + guint id, ncounters = 0, total_sink_count = 0; + BufferCountData *bcd, **bcds = + g_malloc0 (sizeof (BufferCountData *) * element->numpads); + gboolean ret = TRUE; + + /* First gather all counts, and free memory, etc */ + iterator = gst_element_iterate_pads (element); + while (!done) { + switch (gst_iterator_next (iterator, &value)) { + case GST_ITERATOR_OK: + pad = g_value_get_object (&value); + bcd = g_object_get_data (G_OBJECT (pad), "buffer-count-data"); + if (GST_PAD_IS_SINK (pad)) { + bcds[++ncounters] = bcd; + total_sink_count += bcd->counter; + } else { + bcds[0] = bcd; + } + gst_pad_remove_probe (pad, bcd->probe_id); + g_value_reset (&value); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iterator); + break; + case GST_ITERATOR_ERROR: + done = TRUE; + *error_message = g_strdup ("Failed to iterate through pads"); + ret = FALSE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (iterator); + + if (!ret) { + g_free (bcds); + return FALSE; + } + + /* Now bcd[0] contains the total number of buffers received, + and subsequent bcd slots contain the total number of buffers sent + by each source pad. Check that the totals match, and that every + source pad got at least one buffer. + Or that's the theory. It doesn't work in practice, the number of + raw buffers flowing is non deterministic. */ +#if 0 + if (bcds[0]->counter != total_sink_count) { + *error_message = g_strdup_printf ("%u buffers received, %u buffers sent", + total_sink_count, bcds[0]->counter); + ret = FALSE; + } + for (id = 1; id < element->numpads; ++id) { + if (bcds[id]->counter == 0) { + *error_message = + g_strdup_printf ("Sink pad %s got no buffers", + GST_PAD_NAME (bcds[id]->pad)); + ret = FALSE; + } + } +#endif + /* We at least check that at least one buffer was sent while the + selected sink was a given sink, for all sinks */ + for (id = 1; id < element->numpads; ++id) { + if (bcds[id]->back_counter == 0) { + *error_message = + g_strdup_printf ("No buffer was sent while sink pad %s was active", + GST_PAD_NAME (bcds[id]->pad)); + ret = FALSE; + } + } + + for (id = 0; id < element->numpads; ++id) { + gst_object_unref (bcds[id]->pad); + g_slice_free (BufferCountData, bcds[id]); + } + g_free (bcds); + return ret; +} + +static GstPad * +find_next_pad (GstElement * element, GstPad * pad) +{ + GstIterator *iterator; + gboolean done = FALSE, pick = FALSE; + GstPad *tmp, *next = NULL, *first = NULL; + GValue value = { 0, }; + + iterator = gst_element_iterate_sink_pads (element); + + while (!done) { + switch (gst_iterator_next (iterator, &value)) { + case GST_ITERATOR_OK: + tmp = g_value_dup_object (&value); + if (first == NULL) + first = gst_object_ref (tmp); + if (pick) { + next = tmp; + done = TRUE; + } else { + pick = (tmp == pad); + gst_object_unref (tmp); + } + g_value_reset (&value); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iterator); + break; + case GST_ITERATOR_ERROR: + done = TRUE; + break; + case GST_ITERATOR_DONE: + /* When we reach the end, we may be in the case where the pad + to search from was the last one in the list, in which case + we want to return the first pad. */ + if (pick) { + next = first; + first = NULL; + } + done = TRUE; + break; + } + } + gst_iterator_free (iterator); + if (first) + gst_object_unref (first); + return next; +} + +static int +find_input_selector (GValue * value, void *userdata) +{ + GstElement *element = g_value_get_object (value); + g_assert (GST_IS_ELEMENT (element)); + if (g_str_has_prefix (GST_ELEMENT_NAME (element), "inputselector")) { + guint npads; + g_object_get (element, "n-pads", &npads, NULL); + if (npads > 1) + return 0; + } + return !0; +} + +/* This function looks for an input-selector, and, if one is found, + cycle through its sink pads */ +static gboolean +check_track_selection (GstValidateMediaInfo * mi, gchar ** error_message) +{ + GstElement *playbin; + GstElement *videosink, *audiosink; + GstElement *input_selector = NULL; + GstBus *bus; + GstMessage *msg; + gboolean ret = TRUE; + GstStateChangeReturn state_ret; + GstIterator *iterator; + GstPad *original_pad; + static const GstClockTime switch_delay = GST_SECOND * 5; + GValue value = { 0, }; + + playbin = gst_element_factory_make ("playbin", "fc-playbin"); + videosink = gst_element_factory_make ("fakesink", "fc-videosink"); + audiosink = gst_element_factory_make ("fakesink", "fc-audiosink"); + + if (!playbin || !videosink || !audiosink) { + *error_message = g_strdup ("Playbin and/or fakesink not available"); + } + + g_object_set (playbin, "video-sink", videosink, "audio-sink", audiosink, + "uri", mi->uri, NULL); + g_object_set (videosink, "sync", TRUE, NULL); + g_object_set (audiosink, "sync", TRUE, NULL); + + bus = gst_pipeline_get_bus (GST_PIPELINE (playbin)); + + state_ret = gst_element_set_state (playbin, GST_STATE_PAUSED); + if (state_ret == GST_STATE_CHANGE_FAILURE) { + *error_message = g_strdup ("Failed to change pipeline to paused"); + ret = FALSE; + goto end; + } else if (state_ret == GST_STATE_CHANGE_ASYNC) { + msg = + gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, + GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_EOS | GST_MESSAGE_ERROR); + if (msg && GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ASYNC_DONE) { + gst_message_unref (msg); + } else { + ret = FALSE; + *error_message = g_strdup ("Playback finihshed unexpectedly"); + goto end; + } + } + + if (gst_element_set_state (playbin, + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { + *error_message = g_strdup ("Failed to set pipeline to playing"); + ret = FALSE; + goto end; + } + + iterator = gst_bin_iterate_recurse (GST_BIN (playbin)); + if (!gst_iterator_find_custom (iterator, + (GCompareFunc) find_input_selector, &value, NULL)) { + /* It's fine, there's only one if several tracks of the same type */ + gst_iterator_free (iterator); + input_selector = NULL; + goto end; + } + input_selector = g_value_dup_object (&value); + g_value_reset (&value); + gst_iterator_free (iterator); + g_object_get (input_selector, "active-pad", &original_pad, NULL); + if (!original_pad) { + /* Unexpected, log an error somehow ? */ + ret = FALSE; + gst_object_unref (input_selector); + input_selector = NULL; + goto end; + } + + /* Attach a buffer counter to each pad */ + setup_input_selector_counters (input_selector); + + while (1) { + msg = + gst_bus_timed_pop_filtered (bus, switch_delay, + GST_MESSAGE_ERROR | GST_MESSAGE_EOS); + if (msg) { + if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_EOS) { + /* all good */ + ret = TRUE; + } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) { + GError *error = NULL; + gchar *debug = NULL; + + gst_message_parse_error (msg, &error, &debug); + *error_message = g_strdup_printf ("Playback error: %s : %s", + error->message, debug); + g_error_free (error); + g_free (debug); + + ret = FALSE; + } else { + g_assert_not_reached (); + } + gst_message_unref (msg); + } else { + /* Timeout, switch track if we have more, or stop */ + GstPad *active_pad, *next_pad; + + g_object_get (input_selector, "active-pad", &active_pad, NULL); + if (!active_pad) { + *error_message = + g_strdup ("Failed to get active-pad from input-selector"); + ret = FALSE; + goto end; + } + next_pad = find_next_pad (input_selector, active_pad); + gst_object_unref (active_pad); + if (!next_pad) { + ret = FALSE; + goto end; + } + if (next_pad == original_pad) { + goto end; + } + g_object_set (input_selector, "active-pad", next_pad, NULL); + gst_object_unref (next_pad); + } + } + +end: + if (input_selector) { + if (!check_and_remove_input_selector_counters (input_selector, + error_message)) + ret = FALSE; + gst_object_unref (input_selector); + } + gst_object_unref (bus); + gst_element_set_state (playbin, GST_STATE_NULL); + gst_object_unref (playbin); + + return ret; +} + +static gboolean +check_is_image (GstDiscovererInfo * info) +{ + gboolean ret = FALSE; + GList *video_streams = gst_discoverer_info_get_video_streams (info); + + if (g_list_length (video_streams) == 1) { + if (gst_discoverer_video_info_is_image (video_streams->data)) { + GList *audio_streams = gst_discoverer_info_get_audio_streams (info); + + if (audio_streams == NULL) + ret = TRUE; + else + gst_discoverer_stream_info_list_free (audio_streams); + } + } + + gst_discoverer_stream_info_list_free (video_streams); + + return ret; +} + +gboolean +gst_validate_media_info_inspect_uri (GstValidateMediaInfo * mi, + const gchar * uri, gboolean discover_only, GError ** err) +{ + GstDiscovererInfo *info; + GstDiscoverer *discoverer = gst_discoverer_new (GST_SECOND * 60, err); + gboolean ret = TRUE; + + g_return_val_if_fail (uri != NULL, FALSE); + + g_free (mi->uri); + mi->uri = g_strdup (uri); + + if (!discoverer) { + return FALSE; + } + + info = gst_discoverer_discover_uri (discoverer, uri, err); + + if (gst_discoverer_info_get_result (info) != GST_DISCOVERER_OK) { + gst_object_unref (discoverer); + return FALSE; + } + + mi->is_image = check_is_image (info); + ret = check_file_size (mi) & ret; + ret = check_encoding_profile (mi, info) & ret; + ret = check_file_duration (mi, info) & ret; + + if (mi->is_image) + goto done; + + check_seekable (mi, info); + if (discover_only) + goto done; + + ret = check_playback (mi, &mi->playback_error) & ret; + ret = check_reverse_playback (mi, &mi->reverse_playback_error) & ret; + ret = check_track_selection (mi, &mi->track_switch_error) & ret; + +done: + gst_object_unref (discoverer); + + return ret; +} + +gboolean +gst_validate_media_info_compare (GstValidateMediaInfo * expected, + GstValidateMediaInfo * extracted) +{ + gboolean ret = TRUE; + if (expected->duration != extracted->duration) { + gst_validate_printf (NULL, + "Duration changed: %" GST_TIME_FORMAT " -> %" GST_TIME_FORMAT "\n", + GST_TIME_ARGS (expected->duration), + GST_TIME_ARGS (extracted->duration)); + ret = FALSE; + } + if (expected->file_size != extracted->file_size) { + gst_validate_printf (NULL, + "File size changed: %" G_GUINT64_FORMAT " -> %" G_GUINT64_FORMAT "\n", + expected->file_size, extracted->file_size); + ret = FALSE; + } + if (expected->seekable && !extracted->seekable) { + gst_validate_printf (NULL, "File isn't seekable anymore\n"); + ret = FALSE; + } + + if (extracted->discover_only == FALSE) { + if (expected->playback_error == NULL && extracted->playback_error) { + gst_validate_printf (NULL, "Playback is now failing with: %s\n", + extracted->playback_error); + ret = FALSE; + } + if (expected->reverse_playback_error == NULL + && extracted->reverse_playback_error) { + gst_validate_printf (NULL, "Reverse playback is now failing with: %s\n", + extracted->reverse_playback_error); + ret = FALSE; + } + if (expected->track_switch_error == NULL && extracted->track_switch_error) { + gst_validate_printf (NULL, "Track switching is now failing with: %s\n", + extracted->track_switch_error); + ret = FALSE; + } + } + + if (extracted->stream_info == NULL || expected->stream_info == NULL) { + gst_validate_printf (NULL, + "Stream infos could not be retrieved, an error occured\n"); + ret = FALSE; + } else if (expected->stream_info + && !gst_caps_is_equal_fixed (expected->stream_info->caps, + extracted->stream_info->caps)) { + gchar *caps1 = gst_caps_to_string (expected->stream_info->caps); + gchar *caps2 = gst_caps_to_string (extracted->stream_info->caps); + + gst_validate_printf (NULL, "Media caps changed: '%s' -> '%s'\n", caps1, + caps2); + g_free (caps1); + g_free (caps2); + ret = FALSE; + } + return ret; +} diff --git a/validate/gst/validate/gst-validate-media-info.h b/validate/gst/validate/gst-validate-media-info.h new file mode 100644 index 0000000000..9df9bf1fe6 --- /dev/null +++ b/validate/gst/validate/gst-validate-media-info.h @@ -0,0 +1,89 @@ +/* GStreamer + * Copyright (C) 2013 Thiago Santos + * + * gst-validate-media-info.h - Media information structure + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_VALIDATE_MEDIA_INFO_H__ +#define __GST_VALIDATE_MEDIA_INFO_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +typedef struct _GstValidateMediaInfo GstValidateMediaInfo; +typedef struct _GstValidateStreamInfo GstValidateStreamInfo; + +/** + * GstValidateMediaInfo: + * + * GStreamer Validate MediaInfo struct. + * + * Stores extracted information about a media + */ +struct _GstValidateMediaInfo { + + /* */ + /* Value for the expected total duration of the file in nanosecs + * Set to GST_CLOCK_TIME_NONE if it shouldn't be tested */ + GstClockTime duration; + gboolean is_image; + + /* Expected file_size, set to 0 to skip test */ + guint64 file_size; + + gboolean seekable; + + gchar *playback_error; + gchar *reverse_playback_error; + gchar *track_switch_error; + + gchar *uri; + + gboolean discover_only; + + GstValidateStreamInfo *stream_info; +}; + +GST_VALIDATE_API +void gst_validate_media_info_init (GstValidateMediaInfo * mi); +GST_VALIDATE_API +void gst_validate_media_info_clear (GstValidateMediaInfo * mi); +GST_VALIDATE_API +void gst_validate_media_info_free (GstValidateMediaInfo * mi); + +GST_VALIDATE_API +gchar * gst_validate_media_info_to_string (GstValidateMediaInfo * mi, gsize * length); +GST_VALIDATE_API +gboolean gst_validate_media_info_save (GstValidateMediaInfo * mi, const gchar * path, GError ** err); +GST_VALIDATE_API +GstValidateMediaInfo * gst_validate_media_info_load (const gchar * path, GError ** err); + +GST_VALIDATE_API +gboolean gst_validate_media_info_inspect_uri (GstValidateMediaInfo * mi, const gchar * uri, + gboolean discover_only, GError ** err); + +GST_VALIDATE_API +gboolean gst_validate_media_info_compare (GstValidateMediaInfo * expected, GstValidateMediaInfo * extracted); + +G_END_DECLS + +#endif /* __GST_VALIDATE_MEDIA_INFO_H__ */ + diff --git a/validate/gst/validate/gst-validate-mockdecryptor.c b/validate/gst/validate/gst-validate-mockdecryptor.c new file mode 100644 index 0000000000..e07d3789dc --- /dev/null +++ b/validate/gst/validate/gst-validate-mockdecryptor.c @@ -0,0 +1,183 @@ +/* GStreamer + * Copyright (C) 2019 Igalia S.L + * Copyright (C) 2019 Metrological + * Author: Charlie Turner + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gst-validate-mockdecryptor.h" + +#define CLEARKEY_SYSTEM_ID "78f32170-d883-11e0-9572-0800200c9a66" +#define WIDEVINE_SYSTEM_ID "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" + +GST_DEBUG_CATEGORY_STATIC (gst_mockdecryptor_debug); +#define GST_CAT_DEFAULT gst_mockdecryptor_debug + +static GstStaticPadTemplate gst_mockdecryptor_sink_template = + GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS + ("application/x-cenc, original-media-type=(string)video/x-h264, " + GST_PROTECTION_SYSTEM_ID_CAPS_FIELD "=(string)" WIDEVINE_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)audio/mpeg, " + GST_PROTECTION_SYSTEM_ID_CAPS_FIELD "=(string)" WIDEVINE_SYSTEM_ID) + ); + +static GstStaticPadTemplate gst_mockdecryptor_src_template = + GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/webm; " + "audio/webm; " + "video/mp4; " "audio/mp4; " "audio/mpeg; " "video/x-h264")); + +#define _mockdecryptor_do_init \ + GST_DEBUG_CATEGORY_INIT (gst_mockdecryptor_debug, GST_MOCKDECRYPTOR_NAME, 0, "mock decryptor element"); +#define gst_mockdecryptor_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstMockDecryptor, gst_mockdecryptor, + GST_TYPE_BASE_TRANSFORM, _mockdecryptor_do_init); + +static GstCaps *gst_mockdecryptor_transform_caps (GstBaseTransform *, + GstPadDirection, GstCaps *, GstCaps *); +static GstFlowReturn gst_mockdecryptor_transform_in_place (GstBaseTransform *, + GstBuffer *); + +static void +gst_mockdecryptor_class_init (GstMockDecryptorClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstBaseTransformClass *base_transform_class = + GST_BASE_TRANSFORM_CLASS (klass); + + base_transform_class->transform_ip = + GST_DEBUG_FUNCPTR (gst_mockdecryptor_transform_in_place); + base_transform_class->transform_caps = + GST_DEBUG_FUNCPTR (gst_mockdecryptor_transform_caps); + base_transform_class->transform_ip_on_passthrough = FALSE; + + gst_element_class_add_static_pad_template (element_class, + &gst_mockdecryptor_sink_template); + gst_element_class_add_static_pad_template (element_class, + &gst_mockdecryptor_src_template); + + gst_element_class_set_metadata (element_class, + "Mock decryptor element for unit tests", + GST_ELEMENT_FACTORY_KLASS_DECRYPTOR, + "Use in unit tests", "Charlie Turner "); +} + +static void +gst_mockdecryptor_init (GstMockDecryptor * klass) +{ +} + +static GstCaps * +gst_mockdecryptor_transform_caps (GstBaseTransform * base, + GstPadDirection direction, GstCaps * caps, GstCaps * filter) +{ + GstCaps *transformed_caps = NULL; + guint incoming_caps_size, size; + gint duplicate; + guint i; + + if (direction == GST_PAD_UNKNOWN) + return NULL; + + GST_DEBUG_OBJECT (base, + "direction: %s, caps: %" GST_PTR_FORMAT " filter: %" GST_PTR_FORMAT, + (direction == GST_PAD_SRC) ? "src" : "sink", caps, filter); + + transformed_caps = gst_caps_new_empty (); + + incoming_caps_size = gst_caps_get_size (caps); + for (i = 0; i < incoming_caps_size; ++i) { + GstStructure *incoming_structure = gst_caps_get_structure (caps, i); + GstStructure *outgoing_structure = NULL; + guint index; + + if (direction == GST_PAD_SINK) { + if (!gst_structure_has_field (incoming_structure, "original-media-type")) + continue; + + outgoing_structure = gst_structure_copy (incoming_structure); + gst_structure_set_name (outgoing_structure, + gst_structure_get_string (outgoing_structure, "original-media-type")); + + gst_structure_remove_fields (outgoing_structure, "protection-system", + "original-media-type", "encryption-algorithm", "encoding-scope", + "cipher-mode", NULL); + } else { + outgoing_structure = gst_structure_copy (incoming_structure); + + /* Filter out the video related fields from the up-stream caps, + * because they are not relevant to the input caps of this element and + * can cause caps negotiation failures with adaptive bitrate streams. + */ + gst_structure_remove_fields (outgoing_structure, "base-profile", + "codec_data", "height", "framerate", "level", "pixel-aspect-ratio", + "profile", "rate", "width", NULL); + + gst_structure_set (outgoing_structure, + "protection-system", G_TYPE_STRING, WIDEVINE_SYSTEM_ID, + "original-media-type", G_TYPE_STRING, + gst_structure_get_name (incoming_structure), NULL); + + gst_structure_set_name (outgoing_structure, "application/x-cenc"); + } + + duplicate = FALSE; + size = gst_caps_get_size (transformed_caps); + + for (index = 0; !duplicate && index < size; ++index) { + GstStructure *structure = + gst_caps_get_structure (transformed_caps, index); + if (gst_structure_is_equal (structure, outgoing_structure)) + duplicate = TRUE; + } + + if (!duplicate) + gst_caps_append_structure (transformed_caps, outgoing_structure); + else + gst_structure_free (outgoing_structure); + } + + if (filter) { + GstCaps *intersection; + + GST_DEBUG_OBJECT (base, "Using filter caps %" GST_PTR_FORMAT, filter); + intersection = + gst_caps_intersect_full (transformed_caps, filter, + GST_CAPS_INTERSECT_FIRST); + gst_caps_replace (&transformed_caps, intersection); + } + + GST_DEBUG_OBJECT (base, "returning %" GST_PTR_FORMAT, transformed_caps); + return transformed_caps; +} + +static GstFlowReturn +gst_mockdecryptor_transform_in_place (GstBaseTransform * base, + GstBuffer * buffer) +{ + /* We are a mock decryptor, just pass the encrypted buffers through... */ + return GST_FLOW_OK; +} diff --git a/validate/gst/validate/gst-validate-mockdecryptor.h b/validate/gst/validate/gst-validate-mockdecryptor.h new file mode 100644 index 0000000000..a2afbecc27 --- /dev/null +++ b/validate/gst/validate/gst-validate-mockdecryptor.h @@ -0,0 +1,61 @@ +/* GStreamer + * Copyright (C) 2019 Igalia S.L + * Copyright (C) 2019 Metrological + * Author: Charlie Turner + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_MOCKDECRYPTOR_H__ +#define __GST_MOCKDECRYPTOR_H__ + +typedef struct _GstMockDecryptor GstMockDecryptor; +typedef struct _GstMockDecryptorClass GstMockDecryptorClass; + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_MOCKDECRYPTOR \ + (gst_mockdecryptor_get_type ()) +#define GST_MOCKDECRYPTOR(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MOCKDECRYPTOR,GstMockDecryptor)) +#define GST_MOCKDECRYPTOR_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MOCKDECRYPTOR,GstMockDecryptorClass)) +#define GST_IS_MOCKDECRYPTOR(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MOCKDECRYPTOR)) +#define GST_IS_MOCKDECRYPTOR_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MOCKDECRYPTOR)) +#define GST_MOCKDECRYPTOR_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_MOCKDECRYPTOR,GstMockDecryptorClass)) + +#define GST_MOCKDECRYPTOR_NAME "mockdecryptor" +struct _GstMockDecryptor +{ + GstBaseTransform element; +}; + +struct _GstMockDecryptorClass +{ + GstBaseTransformClass parent_class; +}; + +G_GNUC_INTERNAL GType gst_mockdecryptor_get_type (void); + +G_END_DECLS + +#endif /* __GST_MOCKDECRYPTOR_H__ */ diff --git a/validate/gst/validate/gst-validate-monitor-factory.c b/validate/gst/validate/gst-validate-monitor-factory.c new file mode 100644 index 0000000000..5cb5863156 --- /dev/null +++ b/validate/gst/validate/gst-validate-monitor-factory.c @@ -0,0 +1,90 @@ +/* GStreamer + * + * Copyright (C) 2013 Collabora Ltd. + * Author: Thiago Sousa Santos + * + * gst-validate-monitor-factory.c - Validate Element monitors factory utility functions + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:gst-validate-monitor-factory + * @title: GstValidateMonitorFactory + * @short_description: Lets you start monitoring a #GstObject with GstValidate + * + * To start monitoring and thus run GstValidate tests on a #GstPipeline, the only thing to + * do is to instanciate a #GstValidateRunner and then attach a #GstValidateMonitor + * to it with #gst_validate_monitor_factory_create + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gst-validate-monitor-factory.h" +#include "gst-validate-bin-monitor.h" +#include "gst-validate-pipeline-monitor.h" +#include "gst-validate-pad-monitor.h" +#include "gst-validate-override-registry.h" + +/** + * gst_validate_monitor_factory_create: + * @target: The #GstObject to create a #GstValidateMonitor for + * @runner: The #GstValidateRunner to use for the new monitor + * @parent: (nullable): The parent of the new monitor + * + * Create a new monitor for @target and starts monitoring it. + * + * Returns: (transfer full): The newly created #GstValidateMonitor + */ +GstValidateMonitor * +gst_validate_monitor_factory_create (GstObject * target, + GstValidateRunner * runner, GstValidateMonitor * parent) +{ + GstValidateMonitor *monitor = NULL; + g_return_val_if_fail (target != NULL, NULL); + + monitor = g_object_get_data ((GObject *) target, "validate-monitor"); + if (monitor) { + GST_INFO_OBJECT (target, "Is already monitored by %" GST_PTR_FORMAT, + monitor); + + return g_object_ref (monitor); + } + + if (GST_IS_PAD (target)) { + monitor = + GST_VALIDATE_MONITOR_CAST (gst_validate_pad_monitor_new (GST_PAD_CAST + (target), runner, GST_VALIDATE_ELEMENT_MONITOR_CAST (parent))); + } else if (GST_IS_PIPELINE (target)) { + monitor = + GST_VALIDATE_MONITOR_CAST (gst_validate_pipeline_monitor_new + (GST_PIPELINE_CAST (target), runner, parent)); + } else if (GST_IS_BIN (target)) { + monitor = + GST_VALIDATE_MONITOR_CAST (gst_validate_bin_monitor_new (GST_BIN_CAST + (target), runner, parent)); + } else if (GST_IS_ELEMENT (target)) { + monitor = + GST_VALIDATE_MONITOR_CAST (gst_validate_element_monitor_new + (GST_ELEMENT_CAST (target), runner, parent)); + } else { + g_assert_not_reached (); + } + + return monitor; +} diff --git a/validate/gst/validate/gst-validate-monitor-factory.h b/validate/gst/validate/gst-validate-monitor-factory.h new file mode 100644 index 0000000000..24fe9cb788 --- /dev/null +++ b/validate/gst/validate/gst-validate-monitor-factory.h @@ -0,0 +1,39 @@ +/* GStreamer + * Copyright (C) 2013 Thiago Santos + * + * gst-validate-monitor-factory.h - Validate Element monitors factory utility functions + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_VALIDATE_MONITOR_FACTORY_H__ +#define __GST_VALIDATE_MONITOR_FACTORY_H__ + +#include +#include + +#include +#include + +G_BEGIN_DECLS + +GST_VALIDATE_API +GstValidateMonitor * gst_validate_monitor_factory_create (GstObject * target, GstValidateRunner * runner, GstValidateMonitor * parent); + +G_END_DECLS + +#endif /* __GST_VALIDATE_MONITOR_FACTORY_H__ */ + diff --git a/validate/gst/validate/gst-validate-monitor.c b/validate/gst/validate/gst-validate-monitor.c new file mode 100644 index 0000000000..b6b75f6ba0 --- /dev/null +++ b/validate/gst/validate/gst-validate-monitor.c @@ -0,0 +1,497 @@ +/* GStreamer + * + * Copyright (C) 2013 Collabora Ltd. + * Author: Thiago Sousa Santos + * + * gst-validate-monitor.c - Validate Monitor class + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gst-validate-enum-types.h" +#include "gst-validate-internal.h" +#include "gst-validate-monitor.h" +#include "gst-validate-override-registry.h" + +/** + * SECTION:gst-validate-monitor + * @short_description: Base class that wraps a #GObject for Validate checks + * + * TODO + */ + +enum +{ + PROP_0, + PROP_OBJECT, + PROP_PIPELINE, + PROP_RUNNER, + PROP_VALIDATE_PARENT, + PROP_VERBOSITY, + PROP_LAST +}; + +static gboolean gst_validate_monitor_do_setup (GstValidateMonitor * monitor); +static void +gst_validate_monitor_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void +gst_validate_monitor_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static GObject *gst_validate_monitor_constructor (GType type, + guint n_construct_params, GObjectConstructParam * construct_params); + +static gboolean gst_validate_monitor_setup (GstValidateMonitor * monitor); + +static GstValidateInterceptionReturn +gst_validate_monitor_intercept_report (GstValidateReporter * reporter, + GstValidateReport * report); + +#define _do_init \ + G_IMPLEMENT_INTERFACE (GST_TYPE_VALIDATE_REPORTER, _reporter_iface_init) + +static GstValidateReportingDetails +_get_reporting_level (GstValidateReporter * monitor) +{ + return GST_VALIDATE_MONITOR (monitor)->level; +} + +/** + * gst_validate_monitor_get_pipeline: + * @monitor: The monitor to get the pipeline from + * + * Returns: (transfer full): The pipeline in which @monitor + * target is in. + */ +GstPipeline * +gst_validate_monitor_get_pipeline (GstValidateMonitor * monitor) +{ + return g_weak_ref_get (&monitor->pipeline); +} + +/** + * gst_validate_monitor_get_target: + * @monitor: The monitor to get the target from + * + * Returns: (transfer full): The target object + */ +GstObject * +gst_validate_monitor_get_target (GstValidateMonitor * monitor) +{ + return g_weak_ref_get (&monitor->target); +} + +static GstPipeline * +_get_pipeline (GstValidateReporter * monitor) +{ + return g_weak_ref_get (&(GST_VALIDATE_MONITOR (monitor)->pipeline)); +} + +static void +_reporter_iface_init (GstValidateReporterInterface * iface) +{ + iface->intercept_report = gst_validate_monitor_intercept_report; + iface->get_reporting_level = _get_reporting_level; + iface->get_pipeline = _get_pipeline; +} + +#define gst_validate_monitor_parent_class parent_class +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstValidateMonitor, gst_validate_monitor, + GST_TYPE_OBJECT, _do_init); + +static void +gst_validate_monitor_dispose (GObject * object) +{ + GstValidateMonitor *monitor = GST_VALIDATE_MONITOR_CAST (object); + + g_mutex_clear (&monitor->mutex); + g_mutex_clear (&monitor->overrides_mutex); + g_queue_clear (&monitor->overrides); + + g_weak_ref_clear (&monitor->pipeline); + g_weak_ref_clear (&monitor->target); + + if (monitor->media_descriptor) + gst_object_unref (monitor->media_descriptor); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_validate_monitor_finalize (GObject * object) +{ + gst_validate_reporter_set_name (GST_VALIDATE_REPORTER (object), NULL); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_validate_monitor_class_init (GstValidateMonitorClass * klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->get_property = gst_validate_monitor_get_property; + gobject_class->set_property = gst_validate_monitor_set_property; + gobject_class->dispose = gst_validate_monitor_dispose; + gobject_class->finalize = gst_validate_monitor_finalize; + gobject_class->constructor = gst_validate_monitor_constructor; + + klass->setup = gst_validate_monitor_do_setup; + + g_object_class_install_property (gobject_class, PROP_OBJECT, + g_param_spec_object ("object", "Object", "The object to be monitored", + G_TYPE_OBJECT, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, PROP_PIPELINE, + g_param_spec_object ("pipeline", "Pipeline", "The pipeline in which the" + "monitored object is", GST_TYPE_PIPELINE, + G_PARAM_CONSTRUCT | G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, PROP_RUNNER, + g_param_spec_object ("validate-runner", "VALIDATE Runner", + "The Validate runner to report errors to", + GST_TYPE_VALIDATE_RUNNER, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, PROP_VALIDATE_PARENT, + g_param_spec_object ("validate-parent", "VALIDATE parent monitor", + "The Validate monitor that is the parent of this one", + GST_TYPE_VALIDATE_MONITOR, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, PROP_VERBOSITY, + g_param_spec_flags ("verbosity", "Verbosity", + "The verbosity of GstValidate on the monitor", + GST_TYPE_VALIDATE_VERBOSITY_FLAGS, + GST_VALIDATE_VERBOSITY_POSITION, G_PARAM_READWRITE)); +} + +static GObject * +gst_validate_monitor_constructor (GType type, guint n_construct_params, + GObjectConstructParam * construct_params) +{ + GstObject *target; + GstValidateMonitor *monitor = + GST_VALIDATE_MONITOR_CAST (G_OBJECT_CLASS (parent_class)->constructor + (type, + n_construct_params, + construct_params)); + + if (monitor->parent) { + GstPipeline *parent_pipeline = + gst_validate_monitor_get_pipeline (monitor->parent); + + gst_validate_monitor_set_media_descriptor (monitor, + monitor->parent->media_descriptor); + + if (parent_pipeline) { + g_weak_ref_init (&monitor->pipeline, parent_pipeline); + + gst_object_unref (parent_pipeline); + } + } + + gst_validate_monitor_setup (monitor); + gst_validate_override_registry_attach_overrides (monitor); + + target = gst_validate_monitor_get_target (monitor); + g_object_set_data ((GObject *) target, "validate-monitor", monitor); + gst_object_unref (target); + + return (GObject *) monitor; +} + +static void +gst_validate_monitor_init (GstValidateMonitor * monitor) +{ + g_mutex_init (&monitor->mutex); + + g_mutex_init (&monitor->overrides_mutex); + g_queue_init (&monitor->overrides); + + monitor->verbosity = GST_VALIDATE_VERBOSITY_POSITION; +} + +static gboolean +gst_validate_monitor_do_setup (GstValidateMonitor * monitor) +{ + /* NOP */ + return TRUE; +} + +static GstValidateReportingDetails +_get_report_level_for_pad (GstValidateRunner * runner, GstObject * pad) +{ + gchar *name; + GstValidateReportingDetails level = GST_VALIDATE_SHOW_UNKNOWN; + + name = g_strdup_printf ("%s__%s", GST_DEBUG_PAD_NAME (pad)); + level = gst_validate_runner_get_reporting_level_for_name (runner, name); + + g_free (name); + return level; +} + +static void +_determine_reporting_level (GstValidateMonitor * monitor) +{ + GstValidateRunner *runner; + GstObject *object, *parent; + gchar *object_name; + GstValidateReportingDetails level = GST_VALIDATE_SHOW_UNKNOWN; + + object = gst_validate_monitor_get_target (monitor); + runner = gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (monitor)); + + do { + if (!GST_IS_OBJECT (object)) + break; + + /* Let's allow for singling out pads */ + if (GST_IS_PAD (object)) { + level = _get_report_level_for_pad (runner, object); + if (level != GST_VALIDATE_SHOW_UNKNOWN) + break; + } + + object_name = gst_object_get_name (object); + level = gst_validate_runner_get_reporting_level_for_name (runner, + object_name); + parent = gst_object_get_parent (object); + gst_object_unref (object); + object = parent; + g_free (object_name); + } while (object && level == GST_VALIDATE_SHOW_UNKNOWN); + + if (object) + gst_object_unref (object); + + if (runner) + gst_object_unref (runner); + + monitor->level = level; +} + +gboolean +gst_validate_monitor_setup (GstValidateMonitor * monitor) +{ + GList *config; + + GST_DEBUG_OBJECT (monitor, "Starting monitor setup"); + + for (config = gst_validate_plugin_get_config (NULL); config; + config = config->next) { + const gchar *verbosity = + gst_structure_get_string (GST_STRUCTURE (config->data), + "verbosity"); + + if (verbosity) + gst_util_set_object_arg (G_OBJECT (monitor), "verbosity", verbosity); + } + + /* For now we just need to do this at setup time */ + _determine_reporting_level (monitor); + return GST_VALIDATE_MONITOR_GET_CLASS (monitor)->setup (monitor); +} + +/** + * gst_validate_monitor_get_element + * @monitor: The monitor + * + * Returns: (transfer none): The GstElement associated with @monitor + */ +GstElement * +gst_validate_monitor_get_element (GstValidateMonitor * monitor) +{ + GstValidateMonitorClass *klass = GST_VALIDATE_MONITOR_GET_CLASS (monitor); + GstElement *element = NULL; + + if (klass->get_element) + element = klass->get_element (monitor); + + return element; +} + +gchar * +gst_validate_monitor_get_element_name (GstValidateMonitor * monitor) +{ + gchar *res = NULL; + GstElement *element; + + element = gst_validate_monitor_get_element (monitor); + if (element) { + res = g_strdup (GST_ELEMENT_NAME (element)); + gst_object_unref (element); + } + + return res; +} + +/* Check if any of our overrides wants to change the report severity */ +static GstValidateInterceptionReturn +gst_validate_monitor_intercept_report (GstValidateReporter * reporter, + GstValidateReport * report) +{ + GList *iter; + GstValidateMonitor *monitor = GST_VALIDATE_MONITOR_CAST (reporter); + + GST_VALIDATE_MONITOR_OVERRIDES_LOCK (monitor); + for (iter = monitor->overrides.head; iter; iter = g_list_next (iter)) { + report->level = + gst_validate_override_get_severity (iter->data, + gst_validate_issue_get_id (report->issue), report->level); + } + GST_VALIDATE_MONITOR_OVERRIDES_UNLOCK (monitor); + + return GST_VALIDATE_REPORTER_REPORT; +} + +void +gst_validate_monitor_attach_override (GstValidateMonitor * monitor, + GstValidateOverride * override) +{ + GstValidateRunner *runner; + GstValidateRunner *mrunner; + + if (!gst_validate_override_can_attach (override, monitor)) { + GST_INFO_OBJECT (monitor, "Can not attach override %s", + gst_validate_reporter_get_name (GST_VALIDATE_REPORTER (override))); + + return; + } + + runner = gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (override)); + mrunner = gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (monitor)); + GST_VALIDATE_MONITOR_OVERRIDES_LOCK (monitor); + if (runner) { + g_assert (runner == mrunner); + } else + gst_validate_reporter_set_runner (GST_VALIDATE_REPORTER (override), + mrunner); + g_queue_push_tail (&monitor->overrides, override); + GST_VALIDATE_MONITOR_OVERRIDES_UNLOCK (monitor); + + if (runner) + gst_object_unref (runner); + if (mrunner) + gst_object_unref (mrunner); + gst_validate_override_attached (override); +} + +static void +gst_validate_monitor_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstValidateMonitor *monitor; + + monitor = GST_VALIDATE_MONITOR_CAST (object); + + switch (prop_id) { + case PROP_OBJECT: + { + GstObject *target; + + target = g_value_get_object (value); + + g_assert (gst_validate_monitor_get_target (monitor) == NULL); + g_weak_ref_init (&monitor->target, target); + + if (GST_IS_OBJECT (target)) + gst_validate_reporter_set_name (GST_VALIDATE_REPORTER (monitor), + gst_object_get_name (target)); + + break; + } + case PROP_PIPELINE: + g_weak_ref_init (&monitor->pipeline, g_value_get_object (value)); + break; + case PROP_RUNNER: + gst_validate_reporter_set_runner (GST_VALIDATE_REPORTER (monitor), + g_value_get_object (value)); + break; + case PROP_VALIDATE_PARENT: + monitor->parent = g_value_get_object (value); + break; + case PROP_VERBOSITY: + monitor->verbosity = g_value_get_flags (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_validate_monitor_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstValidateMonitor *monitor; + + monitor = GST_VALIDATE_MONITOR_CAST (object); + + switch (prop_id) { + case PROP_OBJECT: + g_value_take_object (value, gst_validate_monitor_get_target (monitor)); + break; + case PROP_PIPELINE: + g_value_take_object (value, gst_validate_monitor_get_pipeline (monitor)); + break; + case PROP_RUNNER: + g_value_take_object (value, + gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (monitor))); + break; + case PROP_VALIDATE_PARENT: + g_value_set_object (value, GST_VALIDATE_MONITOR_GET_PARENT (monitor)); + break; + case PROP_VERBOSITY: + g_value_set_flags (value, monitor->verbosity); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +void +gst_validate_monitor_set_media_descriptor (GstValidateMonitor * monitor, + GstValidateMediaDescriptor * media_descriptor) +{ + GstValidateMonitorClass *klass = GST_VALIDATE_MONITOR_GET_CLASS (monitor); + + GST_DEBUG_OBJECT (monitor, "Set media desc: %" GST_PTR_FORMAT, + media_descriptor); + if (monitor->media_descriptor) + gst_object_unref (monitor->media_descriptor); + + if (media_descriptor) + gst_object_ref (media_descriptor); + + monitor->media_descriptor = media_descriptor; + if (klass->set_media_descriptor) + klass->set_media_descriptor (monitor, media_descriptor); +} + +GstValidateMonitor * +gst_validate_get_monitor (GObject * object) +{ + return GST_VALIDATE_MONITOR (g_object_get_data (object, "validate-monitor")); +} diff --git a/validate/gst/validate/gst-validate-monitor.h b/validate/gst/validate/gst-validate-monitor.h new file mode 100644 index 0000000000..2964705f23 --- /dev/null +++ b/validate/gst/validate/gst-validate-monitor.h @@ -0,0 +1,142 @@ +/* GStreamer + * Copyright (C) 2013 Thiago Santos + * + * gst-validate-monitor.h - Validate Monitor abstract base class + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_VALIDATE_MONITOR_H__ +#define __GST_VALIDATE_MONITOR_H__ + +#include +#include + +typedef struct _GstValidateMonitor GstValidateMonitor; +typedef struct _GstValidateMonitorClass GstValidateMonitorClass; + +#include +#include +#include +#include +#include + +G_BEGIN_DECLS + +#ifndef __GI_SCANNER__ +#define GST_TYPE_VALIDATE_MONITOR (gst_validate_monitor_get_type ()) +#define GST_IS_VALIDATE_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_VALIDATE_MONITOR)) +#define GST_IS_VALIDATE_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_VALIDATE_MONITOR)) +#define GST_VALIDATE_MONITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_VALIDATE_MONITOR, GstValidateMonitorClass)) +#define GST_VALIDATE_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_VALIDATE_MONITOR, GstValidateMonitor)) +#define GST_VALIDATE_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_VALIDATE_MONITOR, GstValidateMonitorClass)) +#define GST_VALIDATE_MONITOR_CAST(obj) ((GstValidateMonitor*)(obj)) +#define GST_VALIDATE_MONITOR_CLASS_CAST(klass) ((GstValidateMonitorClass*)(klass)) +#endif + +#define GST_VALIDATE_MONITOR_GET_PARENT(m) (GST_VALIDATE_MONITOR_CAST (m)->parent) + +#define GST_VALIDATE_MONITOR_LOCK(m) \ + G_STMT_START { \ + GST_LOG_OBJECT (m, "About to lock %p", &GST_VALIDATE_MONITOR_CAST(m)->mutex); \ + (g_mutex_lock (&GST_VALIDATE_MONITOR_CAST(m)->mutex)); \ + GST_LOG_OBJECT (m, "Acquired lock %p", &GST_VALIDATE_MONITOR_CAST(m)->mutex); \ + } G_STMT_END + +#define GST_VALIDATE_MONITOR_UNLOCK(m) \ + G_STMT_START { \ + GST_LOG_OBJECT (m, "About to unlock %p", &GST_VALIDATE_MONITOR_CAST(m)->mutex); \ + (g_mutex_unlock (&GST_VALIDATE_MONITOR_CAST(m)->mutex)); \ + GST_LOG_OBJECT (m, "unlocked %p", &GST_VALIDATE_MONITOR_CAST(m)->mutex); \ + } G_STMT_END + +#define GST_VALIDATE_MONITOR_OVERRIDES_LOCK(m) g_mutex_lock (&GST_VALIDATE_MONITOR_CAST (m)->overrides_mutex) +#define GST_VALIDATE_MONITOR_OVERRIDES_UNLOCK(m) g_mutex_unlock (&GST_VALIDATE_MONITOR_CAST (m)->overrides_mutex) +#define GST_VALIDATE_MONITOR_OVERRIDES(m) (GST_VALIDATE_MONITOR_CAST (m)->overrides) + +/* #else TODO Implemen no variadic macros, use inline, + * Problem being: + * GST_VALIDATE_REPORT_LEVEL_ ## status + * GST_VALIDATE_AREA_ ## area ## _ ## subarea + */ + +/** + * GstValidateMonitor: + * + * GStreamer Validate Monitor class. + * + * Class that wraps a #GObject for Validate checks + */ +struct _GstValidateMonitor { + GstObject object; + + GWeakRef target; + GWeakRef pipeline; + GMutex mutex; + gchar *target_name; + + GstValidateMonitor *parent; + + GMutex overrides_mutex; + GQueue overrides; + GstValidateMediaDescriptor *media_descriptor; + + GstValidateReportingDetails level; + + /*< private >*/ + GHashTable *reports; + + GstValidateVerbosityFlags verbosity; +}; + +/** + * GstValidateMonitorClass: + * @parent_class: parent + * + * GStreamer Validate Monitor object class. + */ +struct _GstValidateMonitorClass { + GstObjectClass parent_class; + + gboolean (* setup) (GstValidateMonitor * monitor); + GstElement *(* get_element) (GstValidateMonitor * monitor); + void (*set_media_descriptor) (GstValidateMonitor * monitor, + GstValidateMediaDescriptor * media_descriptor); +}; + +/* normal GObject stuff */ +GST_VALIDATE_API +GType gst_validate_monitor_get_type (void); + +GST_VALIDATE_API +void gst_validate_monitor_attach_override (GstValidateMonitor * monitor, + GstValidateOverride * override); + +GST_VALIDATE_API +GstElement * gst_validate_monitor_get_element (GstValidateMonitor * monitor); +GST_VALIDATE_API +gchar * gst_validate_monitor_get_element_name (GstValidateMonitor * monitor); +GST_VALIDATE_API +void gst_validate_monitor_set_media_descriptor (GstValidateMonitor * monitor, + GstValidateMediaDescriptor *media_descriptor); +GST_VALIDATE_API +GstPipeline * gst_validate_monitor_get_pipeline (GstValidateMonitor * monitor); +GST_VALIDATE_API +GstObject * gst_validate_monitor_get_target (GstValidateMonitor * monitor); +G_END_DECLS + +#endif /* __GST_VALIDATE_MONITOR_H__ */ + diff --git a/validate/gst/validate/gst-validate-override-registry.c b/validate/gst/validate/gst-validate-override-registry.c new file mode 100644 index 0000000000..586854d6f0 --- /dev/null +++ b/validate/gst/validate/gst-validate-override-registry.c @@ -0,0 +1,526 @@ +/* GStreamer + * + * Copyright (C) 2013 Collabora Ltd. + * Author: Thiago Sousa Santos + * + * gst-validate-override-registry.c - Validate Override Registry + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include + +#include "gst-validate-report.h" +#include "gst-validate-reporter.h" +#include "gst-validate-utils.h" +#include "gst-validate-internal.h" +#include "gst-validate-monitor.h" +#include "gst-validate-override.h" +#include "gst-validate-override-registry.h" + +typedef struct +{ + gchar *name; + GstValidateOverride *override; +} GstValidateOverrideRegistryNameEntry; + +typedef struct +{ + GType gtype; + GstValidateOverride *override; +} GstValidateOverrideRegistryGTypeEntry; + +static GMutex _gst_validate_override_registry_mutex; +static GstValidateOverrideRegistry *_registry_default = NULL; + +#define GST_VALIDATE_OVERRIDE_REGISTRY_LOCK(r) g_mutex_lock (&r->mutex) +#define GST_VALIDATE_OVERRIDE_REGISTRY_UNLOCK(r) g_mutex_unlock (&r->mutex) + +#define GST_VALIDATE_OVERRIDE_INIT_SYMBOL "gst_validate_create_overrides" +typedef int (*GstValidateCreateOverride) (void); + +static void + gst_validate_override_registry_name_entry_free + (GstValidateOverrideRegistryNameEntry * entry) +{ + g_free (entry->name); + g_object_unref (entry->override); + + g_slice_free (GstValidateOverrideRegistryNameEntry, entry); +} + +static void + gst_validate_override_registry_type_entry_free + (GstValidateOverrideRegistryGTypeEntry * entry) +{ + g_object_unref (entry->override); + + g_slice_free (GstValidateOverrideRegistryGTypeEntry, entry); +} + +static GstValidateOverrideRegistry * +gst_validate_override_registry_new (void) +{ + GstValidateOverrideRegistry *reg = g_slice_new0 (GstValidateOverrideRegistry); + + g_mutex_init (®->mutex); + g_queue_init (®->name_overrides); + g_queue_init (®->gtype_overrides); + g_queue_init (®->klass_overrides); + + return reg; +} + +static void +gst_validate_overide_registery_free (GstValidateOverrideRegistry * reg) +{ + g_queue_foreach (®->klass_overrides, + (GFunc) gst_validate_override_registry_name_entry_free, NULL); + + g_queue_foreach (®->name_overrides, + (GFunc) gst_validate_override_registry_name_entry_free, NULL); + + g_queue_foreach (®->gtype_overrides, + (GFunc) gst_validate_override_registry_type_entry_free, NULL); + + g_queue_clear (®->name_overrides); + g_queue_clear (®->gtype_overrides); + g_queue_clear (®->klass_overrides); + g_mutex_clear (®->mutex); + + g_slice_free (GstValidateOverrideRegistry, reg); +} + +/** + * gst_validate_override_registry_get: (skip): + */ +GstValidateOverrideRegistry * +gst_validate_override_registry_get (void) +{ + g_mutex_lock (&_gst_validate_override_registry_mutex); + if (G_UNLIKELY (!_registry_default)) { + _registry_default = gst_validate_override_registry_new (); + } + g_mutex_unlock (&_gst_validate_override_registry_mutex); + + return _registry_default; +} + +void +gst_validate_override_register_by_name (const gchar * name, + GstValidateOverride * override) +{ + GstValidateOverrideRegistry *registry = gst_validate_override_registry_get (); + GstValidateOverrideRegistryNameEntry *entry = + g_slice_new (GstValidateOverrideRegistryNameEntry); + + GST_VALIDATE_OVERRIDE_REGISTRY_LOCK (registry); + entry->name = g_strdup (name); + entry->override = g_object_ref (override); + g_queue_push_tail (®istry->name_overrides, entry); + GST_VALIDATE_OVERRIDE_REGISTRY_UNLOCK (registry); +} + +void +gst_validate_override_register_by_type (GType gtype, + GstValidateOverride * override) +{ + GstValidateOverrideRegistry *registry = gst_validate_override_registry_get (); + GstValidateOverrideRegistryGTypeEntry *entry = + g_slice_new (GstValidateOverrideRegistryGTypeEntry); + + GST_VALIDATE_OVERRIDE_REGISTRY_LOCK (registry); + entry->gtype = gtype; + entry->override = g_object_ref (override); + g_queue_push_tail (®istry->gtype_overrides, entry); + GST_VALIDATE_OVERRIDE_REGISTRY_UNLOCK (registry); +} + +void +gst_validate_override_register_by_klass (const gchar * klass, + GstValidateOverride * override) +{ + GstValidateOverrideRegistry *registry = gst_validate_override_registry_get (); + GstValidateOverrideRegistryNameEntry *entry = + g_slice_new (GstValidateOverrideRegistryNameEntry); + + GST_VALIDATE_OVERRIDE_REGISTRY_LOCK (registry); + entry->name = g_strdup (klass); + entry->override = g_object_ref (override); + g_queue_push_tail (®istry->klass_overrides, entry); + GST_VALIDATE_OVERRIDE_REGISTRY_UNLOCK (registry); +} + +static void + gst_validate_override_registry_attach_name_overrides_unlocked + (GstValidateOverrideRegistry * registry, GstValidateMonitor * monitor) +{ + GstValidateOverrideRegistryNameEntry *entry; + GList *iter; + const gchar *name; + + name = gst_validate_reporter_get_name (GST_VALIDATE_REPORTER (monitor)); + for (iter = registry->name_overrides.head; iter; iter = g_list_next (iter)) { + entry = iter->data; + if (g_regex_match_simple (entry->name, name, 0, 0)) { + GST_INFO ("%p Adding override %s to %s", registry, entry->name, name); + + gst_validate_monitor_attach_override (monitor, entry->override); + } + } +} + +static void + gst_validate_override_registry_attach_gtype_overrides_unlocked + (GstValidateOverrideRegistry * registry, GstValidateMonitor * monitor) +{ + GstValidateOverrideRegistryGTypeEntry *entry; + GstElement *element; + GList *iter; + + element = gst_validate_monitor_get_element (monitor); + if (!element) + return; + + for (iter = registry->gtype_overrides.head; iter; iter = g_list_next (iter)) { + entry = iter->data; + if (G_TYPE_CHECK_INSTANCE_TYPE (element, entry->gtype)) { + gst_validate_monitor_attach_override (monitor, entry->override); + } + } + gst_object_unref (element); +} + +static void + gst_validate_override_registry_attach_klass_overrides_unlocked + (GstValidateOverrideRegistry * registry, GstValidateMonitor * monitor) +{ + GstValidateOverrideRegistryNameEntry *entry; + GList *iter; + GstElement *element; + + element = gst_validate_monitor_get_element (monitor); + if (!element) + return; + + for (iter = registry->klass_overrides.head; iter; iter = g_list_next (iter)) { + + entry = iter->data; + + if (gst_validate_element_has_klass (element, entry->name)) { + gst_validate_monitor_attach_override (monitor, entry->override); + } + } + gst_object_unref (element); +} + +void +gst_validate_override_registry_attach_overrides (GstValidateMonitor * monitor) +{ + GstValidateOverrideRegistry *reg = gst_validate_override_registry_get (); + + GST_VALIDATE_OVERRIDE_REGISTRY_LOCK (reg); + gst_validate_override_registry_attach_name_overrides_unlocked (reg, monitor); + gst_validate_override_registry_attach_gtype_overrides_unlocked (reg, monitor); + gst_validate_override_registry_attach_klass_overrides_unlocked (reg, monitor); + GST_VALIDATE_OVERRIDE_REGISTRY_UNLOCK (reg); +} + +enum +{ + WRONG_FILE, + WRONG_OVERRIDES, + OK +}; + +static gboolean +_add_override_from_struct (GstStructure * soverride) +{ + GQuark issue_id; + GstValidateReportLevel level; + GstValidateOverride *override; + const gchar *str_issue_id, *str_new_severity, + *factory_name = NULL, *name = NULL, *klass = NULL; + + gboolean registered = FALSE; + + if (!gst_structure_has_name (soverride, "change-severity") + && !gst_structure_has_name (soverride, "change-issue-severity")) { + gst_validate_abort + ("Currently only 'change-severity' overrides are supported"); + + return FALSE; + } + + str_issue_id = gst_structure_get_string (soverride, "issue-id"); + if (!str_issue_id) { + gst_validate_abort ("No issue id provided in override: %" GST_PTR_FORMAT, + soverride); + + return FALSE; + } + + issue_id = g_quark_from_string (str_issue_id); + if (gst_validate_issue_from_id (issue_id) == NULL) { + gst_validate_abort ("No GstValidateIssue registered for %s", str_issue_id); + + return FALSE; + } + + str_new_severity = gst_structure_get_string (soverride, "new-severity"); + if (str_new_severity == NULL) { + gst_validate_abort ("No 'new-severity' field found in %" GST_PTR_FORMAT, + soverride); + + return FALSE; + } + + level = gst_validate_report_level_from_name (str_new_severity); + if (level == GST_VALIDATE_REPORT_LEVEL_UNKNOWN) { + gst_validate_abort ("Unknown level name %s", str_new_severity); + + return FALSE; + } + gst_validate_printf (NULL, "**-> Changing issue '%s' severity to: '%s'\n", + str_issue_id, str_new_severity); + + override = gst_validate_override_new (); + gst_validate_override_change_severity (override, issue_id, level); + + name = gst_structure_get_string (soverride, "element-name"); + klass = gst_structure_get_string (soverride, "element-classification"); + factory_name = gst_structure_get_string (soverride, "element-factory-name"); + + if (factory_name) { + GType type; + GstElement *element = gst_element_factory_make (factory_name, NULL); + + if (element == NULL) { + GST_ERROR ("Unknown element factory name: %s (gst is %sinitialized)", + factory_name, gst_is_initialized ()? "" : "NOT "); + + if (!name && !klass) + return FALSE; + } else { + type = G_OBJECT_TYPE (element); + gst_validate_override_register_by_type (type, override); + gst_object_unref (element); + } + + registered = TRUE; + } + + if (name) { + gst_validate_override_register_by_name (name, override); + registered = TRUE; + } + + if (klass) { + gst_validate_override_register_by_klass (klass, override); + registered = TRUE; + } + + if (!registered) { + GstValidateIssue *issue = gst_validate_issue_from_id (issue_id); + + if (!issue) { + + g_object_unref (override); + return FALSE; + } + + gst_validate_issue_set_default_level (issue, level); + } + + g_object_unref (override); + return TRUE; +} + +static gboolean +_load_text_override_file (const gchar * filename) +{ + gint ret = OK; + GList *structs = + gst_validate_utils_structs_parse_from_filename (filename, NULL, NULL); + + if (structs) { + GList *tmp; + + for (tmp = structs; tmp; tmp = tmp->next) { + GstStructure *_struct = (GstStructure *) tmp->data; + if (!_add_override_from_struct (_struct)) { + GST_ERROR ("Wrong overrides %" GST_PTR_FORMAT, _struct); + ret = WRONG_OVERRIDES; + } + } + + goto done; + } + + ret = WRONG_FILE; + +done: + g_list_free_full (structs, (GDestroyNotify) gst_structure_free); + return ret; +} + +int +gst_validate_override_registry_preload (void) +{ + gchar **modlist, *const *modname; + const char *sos, *loaderr; + GModule *module; + int ret, nloaded = 0; + gpointer ext_create_overrides; + GList *tmp, *overrides = gst_validate_get_config ("change-issue-severity"); + + for (tmp = overrides; tmp; tmp = tmp->next) + _add_override_from_struct (tmp->data); + g_list_free (overrides); + + sos = g_getenv ("GST_VALIDATE_OVERRIDE"); + if (!sos) { + GST_INFO ("No GST_VALIDATE_OVERRIDE found, no overrides to load"); + return 0; + } + + modlist = g_strsplit (sos, G_SEARCHPATH_SEPARATOR_S, 0); + for (modname = modlist; *modname; ++modname) { + GST_INFO ("Loading overrides from %s", *modname); + module = g_module_open (*modname, G_MODULE_BIND_LAZY); + if (module == NULL) { + + if (_load_text_override_file (*modname) == WRONG_FILE) { + loaderr = g_module_error (); + GST_ERROR ("Failed to load %s %s", *modname, + loaderr ? loaderr : "no idea why"); + } + + continue; + } + if (g_module_symbol (module, GST_VALIDATE_OVERRIDE_INIT_SYMBOL, + &ext_create_overrides)) { + ret = ((GstValidateCreateOverride) ext_create_overrides) (); + if (ret > 0) { + GST_INFO ("Loaded %d overrides from %s", ret, *modname); + nloaded += ret; + } else if (ret < 0) { + GST_WARNING ("Error loading overrides from %s", *modname); + } else { + GST_INFO ("Loaded no overrides from %s", *modname); + } + } else { + GST_WARNING (GST_VALIDATE_OVERRIDE_INIT_SYMBOL " not found in %s", + *modname); + } + g_module_close (module); + } + g_strfreev (modlist); + GST_INFO ("%d overrides loaded", nloaded); + return nloaded; +} + +/** + * gst_validate_override_registry_get_override_for_names: (skip): + */ +GList *gst_validate_override_registry_get_override_for_names + (GstValidateOverrideRegistry * reg, const gchar * name, ...) +{ + GList *iter; + GList *ret = NULL; + + if (name) { + va_list varargs; + GstValidateOverrideRegistryNameEntry *entry; + + va_start (varargs, name); + + GST_VALIDATE_OVERRIDE_REGISTRY_LOCK (reg); + while (name) { + for (iter = reg->name_overrides.head; iter; iter = g_list_next (iter)) { + entry = iter->data; + if ((g_strcmp0 (name, entry->name)) == 0) { + ret = g_list_prepend (ret, entry->override); + } + } + name = va_arg (varargs, gchar *); + } + GST_VALIDATE_OVERRIDE_REGISTRY_UNLOCK (reg); + + va_end (varargs); + } + + return ret; +} + +/** + * gst_validate_override_registry_get_override_list: + * @registry: the override registry + * + * Returns: (transfer full) (element-type GstValidateOverride): a list of #GstValidateOverride + */ +GList * +gst_validate_override_registry_get_override_list (GstValidateOverrideRegistry * + registry) +{ + GList *all_overrides = NULL; + GList *i; + + GST_VALIDATE_OVERRIDE_REGISTRY_LOCK (registry); + for (i = registry->name_overrides.head; i; i = i->next) { + GstValidateOverrideRegistryNameEntry *entry = + (GstValidateOverrideRegistryNameEntry *) i->data; + if (!g_list_find (all_overrides, entry->override)) + all_overrides = g_list_append (all_overrides, entry->override); + } + for (i = registry->klass_overrides.head; i; i = i->next) { + GstValidateOverrideRegistryNameEntry *entry = + (GstValidateOverrideRegistryNameEntry *) i->data; + if (!g_list_find (all_overrides, entry->override)) + all_overrides = g_list_append (all_overrides, entry->override); + } + for (i = registry->name_overrides.head; i; i = i->next) { + GstValidateOverrideRegistryGTypeEntry *entry = + (GstValidateOverrideRegistryGTypeEntry *) i->data; + if (!g_list_find (all_overrides, entry->override)) + all_overrides = g_list_append (all_overrides, entry->override); + } + GST_VALIDATE_OVERRIDE_REGISTRY_UNLOCK (registry); + + return all_overrides; +} + +void +_priv_validate_override_registry_deinit (void) +{ + GstValidateOverrideRegistry *reg = NULL; + + g_mutex_lock (&_gst_validate_override_registry_mutex); + if (G_UNLIKELY (_registry_default)) { + reg = _registry_default; + _registry_default = NULL; + } + g_mutex_unlock (&_gst_validate_override_registry_mutex); + + if (reg) + gst_validate_overide_registery_free (reg); +} diff --git a/validate/gst/validate/gst-validate-override-registry.h b/validate/gst/validate/gst-validate-override-registry.h new file mode 100644 index 0000000000..7dfcdb318a --- /dev/null +++ b/validate/gst/validate/gst-validate-override-registry.h @@ -0,0 +1,66 @@ +/* GStreamer + * Copyright (C) 2013 Thiago Santos + * + * gst-validate-override-registry.h - Validate Override registry + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_VALIDATE_OVERRIDE_REGISTRY_H__ +#define __GST_VALIDATE_OVERRIDE_REGISTRY_H__ + +#include +#include + +#include +#include +#include + +G_BEGIN_DECLS + +typedef struct { + GMutex mutex; + + GQueue name_overrides; + GQueue gtype_overrides; + GQueue klass_overrides; +} GstValidateOverrideRegistry; + +GST_VALIDATE_API +GstValidateOverrideRegistry * gst_validate_override_registry_get (void); + +GST_VALIDATE_API GList * +gst_validate_override_registry_get_override_for_names (GstValidateOverrideRegistry *reg, + const gchar *name, ...); +GST_VALIDATE_API GList * +gst_validate_override_registry_get_override_list (GstValidateOverrideRegistry *registry); +GST_VALIDATE_API +void gst_validate_override_register_by_name (const gchar * name, GstValidateOverride * override); +GST_VALIDATE_API +void gst_validate_override_register_by_type (GType gtype, GstValidateOverride * override); +GST_VALIDATE_API +void gst_validate_override_register_by_klass (const gchar * klass, GstValidateOverride * override); + +GST_VALIDATE_API +void gst_validate_override_registry_attach_overrides (GstValidateMonitor * monitor); + +GST_VALIDATE_API +int gst_validate_override_registry_preload (void); + +G_END_DECLS + +#endif /* __GST_VALIDATE_OVERRIDE_REGISTRY_H__ */ + diff --git a/validate/gst/validate/gst-validate-override.c b/validate/gst/validate/gst-validate-override.c new file mode 100644 index 0000000000..fc2ceecbe7 --- /dev/null +++ b/validate/gst/validate/gst-validate-override.c @@ -0,0 +1,332 @@ +/* GStreamer + * + * Copyright (C) 2013 Collabora Ltd. + * Author: Thiago Sousa Santos + * Copyright (C) 2015 Raspberry Pi Foundation + * Author: Thibault Saunier + * + * gst-validate-override.c - Validate Override that allows customizing Validate behavior + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/** + * SECTION: gst-validate-override + * @title: GstValidateOverride + * @short_description: TODO + * + * TODO + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include "gst-validate-internal.h" +#include "gst-validate-override.h" + +/* *INDENT-OFF* */ + +struct _GstValidateOverridePrivate +{ + GHashTable *level_override; +}; + +enum +{ + PROP_FIRST_PROP = 1, + PROP_RUNNER, + PROP_LAST +}; + +G_DEFINE_TYPE_WITH_CODE (GstValidateOverride, gst_validate_override, + GST_TYPE_OBJECT, G_ADD_PRIVATE (GstValidateOverride) + G_IMPLEMENT_INTERFACE (GST_TYPE_VALIDATE_REPORTER, NULL)) + +/* *INDENT-ON* */ + +static void +_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + case PROP_RUNNER: + g_value_take_object (value, + gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (object))); + break; + default: + break; + } +} + +static void +_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + case PROP_RUNNER: + /* we assume the runner is valid as long as this scenario is, + * no ref taken */ + gst_validate_reporter_set_runner (GST_VALIDATE_REPORTER (object), + g_value_get_object (value)); + break; + default: + break; + } +} + +static void +gst_validate_override_finalize (GObject * object) +{ + GstValidateOverride *self = GST_VALIDATE_OVERRIDE (object); + + void (*chain_up) (GObject *) = + ((GObjectClass *) gst_validate_override_parent_class)->finalize; + + g_hash_table_unref (self->priv->level_override); + + chain_up (object); +} + +static void +gst_validate_override_class_init (GstValidateOverrideClass * klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + oclass->finalize = gst_validate_override_finalize; + + oclass->get_property = _get_property; + oclass->set_property = _set_property; + + g_object_class_install_property (oclass, PROP_RUNNER, + g_param_spec_object ("validate-runner", "VALIDATE Runner", + "The Validate runner to report errors to", + GST_TYPE_VALIDATE_RUNNER, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); +} + +static void +gst_validate_override_init (GstValidateOverride * self) +{ + self->priv = gst_validate_override_get_instance_private (self); + + self->priv->level_override = g_hash_table_new (g_direct_hash, g_direct_equal); +} + +GstValidateOverride * +gst_validate_override_new (void) +{ + return g_object_new (GST_TYPE_VALIDATE_OVERRIDE, NULL); +} + +void +gst_validate_override_change_severity (GstValidateOverride * override, + GstValidateIssueId issue_id, GstValidateReportLevel new_level) +{ + g_hash_table_insert (override->priv->level_override, (gpointer) issue_id, + (gpointer) new_level); +} + +/* + * Also receives @default_level to preserve a custom level that might have + * been set by a previous GstValidateOverride and should not go back to the + * GstValidateIssue default + */ +GstValidateReportLevel +gst_validate_override_get_severity (GstValidateOverride * override, + GstValidateIssueId issue_id, GstValidateReportLevel default_level) +{ + GstValidateReportLevel *level = NULL; + + if (g_hash_table_lookup_extended (override->priv->level_override, + (gpointer) issue_id, NULL, (gpointer) & level)) { + + return GPOINTER_TO_INT (level); + } + return default_level; +} + +/** + * gst_validate_override_set_event_handler: (skip): + */ +void +gst_validate_override_set_event_handler (GstValidateOverride * override, + GstValidateOverrideEventHandler handler) +{ + override->event_handler = handler; +} + +/** + * gst_validate_override_set_buffer_handler : (skip): + */ +void +gst_validate_override_set_buffer_handler (GstValidateOverride * override, + GstValidateOverrideBufferHandler handler) +{ + override->buffer_handler = handler; +} + +/** + * gst_validate_override_set_query_handler: (skip): + */ +void +gst_validate_override_set_query_handler (GstValidateOverride * override, + GstValidateOverrideQueryHandler handler) +{ + override->query_handler = handler; +} + +/** + * gst_validate_override_set_buffer_probe_handler: (skip): + */ +void +gst_validate_override_set_buffer_probe_handler (GstValidateOverride * override, + GstValidateOverrideBufferHandler handler) +{ + override->buffer_probe_handler = handler; +} + +/** + * gst_validate_override_set_getcaps_handler: (skip): + */ +void +gst_validate_override_set_getcaps_handler (GstValidateOverride * override, + GstValidateOverrideGetCapsHandler handler) +{ + override->getcaps_handler = handler; +} + +/** + * gst_validate_override_set_setcaps_handler: (skip): + */ +void +gst_validate_override_set_setcaps_handler (GstValidateOverride * override, + GstValidateOverrideSetCapsHandler handler) +{ + override->setcaps_handler = handler; +} + +/** + * gst_validate_override_event_handler: (skip): + */ +void +gst_validate_override_event_handler (GstValidateOverride * override, + GstValidateMonitor * monitor, GstEvent * event) +{ + if (override->event_handler) + override->event_handler (override, monitor, event); +} + +/** + * gst_validate_override_buffer_handler: (skip): + */ +void +gst_validate_override_buffer_handler (GstValidateOverride * override, + GstValidateMonitor * monitor, GstBuffer * buffer) +{ + if (override->buffer_handler) + override->buffer_handler (override, monitor, buffer); +} + +/** + * gst_validate_override_query_handler: (skip): + */ +void +gst_validate_override_query_handler (GstValidateOverride * override, + GstValidateMonitor * monitor, GstQuery * query) +{ + if (override->query_handler) + override->query_handler (override, monitor, query); +} + +/** + * gst_validate_override_buffer_probe_handler: (skip): + */ +void +gst_validate_override_buffer_probe_handler (GstValidateOverride * override, + GstValidateMonitor * monitor, GstBuffer * buffer) +{ + if (override->buffer_probe_handler) + override->buffer_probe_handler (override, monitor, buffer); +} + +/** + * gst_validate_override_getcaps_handler: (skip): + */ +void +gst_validate_override_getcaps_handler (GstValidateOverride * override, + GstValidateMonitor * monitor, GstCaps * caps) +{ + if (override->getcaps_handler) + override->getcaps_handler (override, monitor, caps); +} + +/** + * gst_validate_override_setcaps_handler: (skip): + */ +void +gst_validate_override_setcaps_handler (GstValidateOverride * override, + GstValidateMonitor * monitor, GstCaps * caps) +{ + if (override->setcaps_handler) + override->setcaps_handler (override, monitor, caps); +} + +/** + * gst_validate_override_element_added_handler: (skip): + */ +void +gst_validate_override_element_added_handler (GstValidateOverride * override, + GstValidateMonitor * monitor, GstElement * child) +{ + if (override->element_added_handler) + override->element_added_handler (override, monitor, child); +} + +/** + * gst_validate_override_set_element_added_handler: (skip): + */ +void +gst_validate_override_set_element_added_handler (GstValidateOverride * override, + GstValidateOverrideElementAddedHandler func) +{ + override->element_added_handler = func; +} + +/** + * gst_validate_override_can_attach: (skip): + */ +gboolean +gst_validate_override_can_attach (GstValidateOverride * override, + GstValidateMonitor * monitor) +{ + GstValidateOverrideClass *klass = GST_VALIDATE_OVERRIDE_GET_CLASS (override); + + if (klass->can_attach) + return klass->can_attach (override, monitor); + + return TRUE; +} + +void +gst_validate_override_attached (GstValidateOverride * override) +{ + GstValidateOverrideClass *klass = GST_VALIDATE_OVERRIDE_GET_CLASS (override); + + if (klass->attached) + klass->attached (override); +} diff --git a/validate/gst/validate/gst-validate-override.h b/validate/gst/validate/gst-validate-override.h new file mode 100644 index 0000000000..93eef630fd --- /dev/null +++ b/validate/gst/validate/gst-validate-override.h @@ -0,0 +1,141 @@ +/* GStreamer + * Copyright (C) 2013 Thiago Santos + * + * gst-validate-override.h - Validate Override that allows customizing Validate behavior + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_VALIDATE_OVERRIDE_H__ +#define __GST_VALIDATE_OVERRIDE_H__ + +#include +#include + +typedef struct _GstValidateOverride GstValidateOverride; +typedef struct _GstValidateOverrideClass GstValidateOverrideClass; +typedef struct _GstValidateOverridePrivate GstValidateOverridePrivate; + + +#include +#include + +G_BEGIN_DECLS + +typedef void (*GstValidateOverrideBufferHandler)(GstValidateOverride * override, + GstValidateMonitor * pad_monitor, GstBuffer * buffer); +typedef void (*GstValidateOverrideEventHandler)(GstValidateOverride * override, + GstValidateMonitor * pad_monitor, GstEvent * event); +typedef void (*GstValidateOverrideQueryHandler)(GstValidateOverride * override, + GstValidateMonitor * pad_monitor, GstQuery * query); +typedef void (*GstValidateOverrideGetCapsHandler)(GstValidateOverride * override, + GstValidateMonitor * pad_monitor, GstCaps * caps); +typedef void (*GstValidateOverrideSetCapsHandler)(GstValidateOverride * override, + GstValidateMonitor * pad_monitor, GstCaps * caps); +typedef void (*GstValidateOverrideElementAddedHandler)(GstValidateOverride * override, + GstValidateMonitor * bin_monitor, GstElement * new_child); + +struct _GstValidateOverrideClass +{ + /**/ + GstObjectClass parent_class; + + gboolean (*can_attach)(GstValidateOverride * override, + GstValidateMonitor * monitor); + + void (*attached)(GstValidateOverride * override); +}; + +struct _GstValidateOverride +{ + GstObject parent; + + GstValidateOverrideBufferHandler buffer_handler; + GstValidateOverrideEventHandler event_handler; + GstValidateOverrideQueryHandler query_handler; + GstValidateOverrideBufferHandler buffer_probe_handler; + GstValidateOverrideGetCapsHandler getcaps_handler; + GstValidateOverrideSetCapsHandler setcaps_handler; + GstValidateOverrideElementAddedHandler element_added_handler; + + /**/ + GstValidateOverridePrivate *priv; +}; + +GST_VALIDATE_API +GType gst_validate_override_get_type (void) G_GNUC_CONST; + +/* TYPE MACROS */ +#ifndef __GI_SCANNER__ +#define GST_TYPE_VALIDATE_OVERRIDE (gst_validate_override_get_type ()) +#define GST_VALIDATE_OVERRIDE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_VALIDATE_OVERRIDE, GstValidateOverride)) +#define GST_VALIDATE_OVERRIDE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_VALIDATE_OVERRIDE, GstValidateOverrideClass)) +#define GST_IS_VALIDATE_OVERRIDE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_VALIDATE_OVERRIDE)) +#define GST_IS_VALIDATE_OVERRIDE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_VALIDATE_OVERRIDE)) +#define GST_VALIDATE_OVERRIDE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_VALIDATE_OVERRIDE, GstValidateOverrideClass)) +#endif + +GST_VALIDATE_API +GstValidateOverride * gst_validate_override_new (void); + +void gst_validate_override_free (GstValidateOverride * override); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstValidateOverride, gst_validate_override_free) + +GST_VALIDATE_API +void gst_validate_override_change_severity (GstValidateOverride * override, GstValidateIssueId issue_id, GstValidateReportLevel new_level); +GST_VALIDATE_API +GstValidateReportLevel gst_validate_override_get_severity (GstValidateOverride * override, GstValidateIssueId issue_id, GstValidateReportLevel default_level); + +GST_VALIDATE_API +void gst_validate_override_event_handler (GstValidateOverride * override, GstValidateMonitor * monitor, GstEvent * event); +GST_VALIDATE_API +void gst_validate_override_buffer_handler (GstValidateOverride * override, GstValidateMonitor * monitor, GstBuffer * buffer); +GST_VALIDATE_API +void gst_validate_override_query_handler (GstValidateOverride * override, GstValidateMonitor * monitor, GstQuery * query); +GST_VALIDATE_API +void gst_validate_override_buffer_probe_handler (GstValidateOverride * override, GstValidateMonitor * monitor, GstBuffer * buffer); +GST_VALIDATE_API +void gst_validate_override_getcaps_handler (GstValidateOverride * override, GstValidateMonitor * monitor, GstCaps * caps); +GST_VALIDATE_API +void gst_validate_override_setcaps_handler (GstValidateOverride * override, GstValidateMonitor * monitor, GstCaps * caps); + +GST_VALIDATE_API +void gst_validate_override_set_event_handler (GstValidateOverride * override, GstValidateOverrideEventHandler handler); +GST_VALIDATE_API +void gst_validate_override_set_buffer_handler (GstValidateOverride * override, GstValidateOverrideBufferHandler handler); +GST_VALIDATE_API +void gst_validate_override_set_query_handler (GstValidateOverride * override, GstValidateOverrideQueryHandler handler); +GST_VALIDATE_API +void gst_validate_override_set_buffer_probe_handler (GstValidateOverride * override, GstValidateOverrideBufferHandler handler); +GST_VALIDATE_API +void gst_validate_override_set_getcaps_handler (GstValidateOverride * override, GstValidateOverrideGetCapsHandler handler); +GST_VALIDATE_API +void gst_validate_override_set_setcaps_handler (GstValidateOverride * override, GstValidateOverrideSetCapsHandler handler); +GST_VALIDATE_API +void gst_validate_override_element_added_handler (GstValidateOverride * override, GstValidateMonitor * monitor, GstElement * child); +GST_VALIDATE_API +void gst_validate_override_set_element_added_handler (GstValidateOverride * override, GstValidateOverrideElementAddedHandler func); + +GST_VALIDATE_API +gboolean gst_validate_override_can_attach (GstValidateOverride * override, GstValidateMonitor *monitor); + +GST_VALIDATE_API +void gst_validate_override_attached (GstValidateOverride * override); + +G_END_DECLS + +#endif /* #ifndef __GST_VALIDATE_OVERRIDE_H__*/ diff --git a/validate/gst/validate/gst-validate-pad-monitor.c b/validate/gst/validate/gst-validate-pad-monitor.c new file mode 100644 index 0000000000..7cf92fc21b --- /dev/null +++ b/validate/gst/validate/gst-validate-pad-monitor.c @@ -0,0 +1,3047 @@ +/* GStreamer + * + * Copyright (C) 2013 Collabora Ltd. + * Author: Thiago Sousa Santos + * + * gst-validate-pad-monitor.c - Validate PadMonitor class + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gst-validate-internal.h" +#include "gst-validate-pad-monitor.h" +#include "gst-validate-element-monitor.h" +#include "gst-validate-pipeline-monitor.h" +#include "gst-validate-reporter.h" +#include "gst-validate-utils.h" +#include "validate.h" +#include +#include + +/** + * SECTION:gst-validate-pad-monitor + * @short_description: Class that wraps a #GstPad for Validate checks + * + * TODO + */ +#define _GET_PAD_MONITOR(p) g_object_get_qdata ((GObject*) p, _Q_VALIDATE_MONITOR) +#define _SET_PAD_MONITOR(p,d) g_object_set_qdata ((GObject*) p, _Q_VALIDATE_MONITOR, d) + +static GstValidateInterceptionReturn +gst_validate_pad_monitor_intercept_report (GstValidateReporter * reporter, + GstValidateReport * report); + +#define _do_init \ + G_IMPLEMENT_INTERFACE (GST_TYPE_VALIDATE_REPORTER, _reporter_iface_init) + +static void +_reporter_iface_init (GstValidateReporterInterface * iface) +{ + iface->intercept_report = gst_validate_pad_monitor_intercept_report; +} + +#define gst_validate_pad_monitor_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstValidatePadMonitor, gst_validate_pad_monitor, + GST_TYPE_VALIDATE_MONITOR, _do_init); + +#define PENDING_FIELDS "pending-fields" +#define AUDIO_TIMESTAMP_TOLERANCE (GST_MSECOND * 100) + +#define PAD_PARENT_IS_DEMUXER(m) \ + (GST_VALIDATE_MONITOR_GET_PARENT(m) ? \ + GST_VALIDATE_ELEMENT_MONITOR_ELEMENT_IS_DEMUXER ( \ + GST_VALIDATE_MONITOR_GET_PARENT(m)) : \ + FALSE) + +#define PAD_PARENT_IS_DECODER(m) \ + (GST_VALIDATE_MONITOR_GET_PARENT(m) ? \ + GST_VALIDATE_ELEMENT_MONITOR_ELEMENT_IS_DECODER ( \ + GST_VALIDATE_MONITOR_GET_PARENT(m)) : \ + FALSE) + +#define PAD_PARENT_IS_ENCODER(m) \ + (GST_VALIDATE_MONITOR_GET_PARENT(m) ? \ + GST_VALIDATE_ELEMENT_MONITOR_ELEMENT_IS_ENCODER ( \ + GST_VALIDATE_MONITOR_GET_PARENT(m)) : \ + FALSE) + +#define PAD_PARENT_IS_SINK(m) \ + (GST_VALIDATE_MONITOR_GET_PARENT(m) ? \ + GST_VALIDATE_ELEMENT_MONITOR_ELEMENT_IS_SINK ( \ + GST_VALIDATE_MONITOR_GET_PARENT(m)) : \ + FALSE) + + +/* + * Locking the parent should always be done before locking the + * pad-monitor to prevent deadlocks in case another monitor from + * another pad on the same element starts an operation that also + * requires locking itself and some other monitors from internally + * linked pads. + * + * An example: + * An element has a sink and a src pad. Some test starts running at sinkpad + * and it locks the parent, and then it locks itself. In case it needs to get + * some information from the srcpad, it is able to lock the srcpad and get it + * because the srcpad should never lock itself before locking the parent (which + * it won't be able as sinkpad already locked it). + * + * As a side one, it is possible that srcpad locks itself without locking the + * parent in case it wants to do a check that won't need to use other internally + * linked pads (sinkpad). But in this case it might lock and unlock freely without + * causing deadlocks. + */ +#define GST_VALIDATE_PAD_MONITOR_PARENT_LOCK(m) \ +G_STMT_START { \ + if (G_LIKELY (GST_VALIDATE_MONITOR_GET_PARENT (m))) { \ + GST_VALIDATE_MONITOR_LOCK (GST_VALIDATE_MONITOR_GET_PARENT (m)); \ + } else { \ + GST_WARNING_OBJECT (m, "No parent found, can't lock"); \ + } \ +} G_STMT_END + +#define GST_VALIDATE_PAD_MONITOR_PARENT_UNLOCK(m) \ +G_STMT_START { \ + if (G_LIKELY (GST_VALIDATE_MONITOR_GET_PARENT (m))) { \ + GST_VALIDATE_MONITOR_UNLOCK (GST_VALIDATE_MONITOR_GET_PARENT (m)); \ + } else { \ + GST_WARNING_OBJECT (m, "No parent found, can't unlock"); \ + } \ +} G_STMT_END + +/* Structure used to store all seek-related information */ +struct _GstValidatePadSeekData +{ + guint32 seqnum; + gdouble rate; + GstFormat format; + GstSeekFlags flags; + GstSeekType start_type, stop_type; + gint64 start, stop; +}; + +typedef struct +{ + GstClockTime timestamp; + GstEvent *event; +} SerializedEventData; + +static GstPad * +_get_actual_pad (GstPad * pad) +{ + pad = gst_object_ref (pad); + + while (GST_IS_PROXY_PAD (pad)) { + GstPad *next_pad; + + if (GST_PAD_IS_SINK (pad)) { + if (GST_IS_GHOST_PAD (pad)) + next_pad = gst_ghost_pad_get_target (GST_GHOST_PAD (pad)); + else + next_pad = GST_PAD (gst_proxy_pad_get_internal (GST_PROXY_PAD (pad))); + } else { + next_pad = gst_pad_get_peer (pad); + } + + gst_object_unref (pad); + if (!next_pad) + return NULL; + + pad = next_pad; + } + + return pad; +} + +static gboolean +_find_master_report_on_pad (GstPad * pad, GstValidateReport * report) +{ + GstValidatePadMonitor *pad_monitor; + GstValidateReport *prev_report; + gboolean result = FALSE; + GstPad *tmppad = pad; + + pad = _get_actual_pad (pad); + if (pad == NULL) { + GST_ERROR_OBJECT (tmppad, "Does not have a target yet"); + + return FALSE; + } + + pad_monitor = _GET_PAD_MONITOR (pad); + + /* For some reason this pad isn't monitored */ + if (pad_monitor == NULL) + goto done; + + prev_report = gst_validate_reporter_get_report ((GstValidateReporter *) + pad_monitor, report->issue->issue_id); + + if (prev_report) { + if (prev_report->master_report) + result = gst_validate_report_set_master_report (report, + prev_report->master_report); + else + result = gst_validate_report_set_master_report (report, prev_report); + } + +done: + gst_object_unref (pad); + + return result; +} + +static gboolean +_find_master_report_for_sink_pad (GstValidatePadMonitor * pad_monitor, + GstValidateReport * report) +{ + GstPad *peerpad; + gboolean result = FALSE; + GstPad *pad = + GST_PAD_CAST (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (pad_monitor))); + + peerpad = gst_pad_get_peer (pad); + gst_object_unref (pad); + + /* If the peer src pad already has a similar report no need to look + * any further */ + if (peerpad && _find_master_report_on_pad (peerpad, report)) + result = TRUE; + + if (peerpad) + gst_object_unref (peerpad); + + return result; +} + +static gboolean +_find_master_report_for_src_pad (GstValidatePadMonitor * pad_monitor, + GstValidateReport * report) +{ + GstIterator *iter; + gboolean done; + gboolean result = FALSE; + GstPad *target = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (pad_monitor))); + + iter = gst_pad_iterate_internal_links (target); + done = FALSE; + while (!done) { + GValue value = { 0, }; + switch (gst_iterator_next (iter, &value)) { + case GST_ITERATOR_OK: + { + GstPad *pad = g_value_get_object (&value); + + if (_find_master_report_on_pad (pad, report)) { + result = TRUE; + done = TRUE; + } + + g_value_reset (&value); + break; + } + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + break; + case GST_ITERATOR_ERROR: + GST_WARNING_OBJECT (target, "Internal links pad iteration error"); + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_object_unref (target); + gst_iterator_free (iter); + + return result; +} + +static GstValidateInterceptionReturn +_concatenate_issues (GstValidatePadMonitor * pad_monitor, + GstValidateReport * report) +{ + GstPad *pad = + GST_PAD_CAST (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (pad_monitor))); + + if (GST_PAD_IS_SINK (pad) + && _find_master_report_for_sink_pad (pad_monitor, report)) { + gst_object_unref (pad); + return GST_VALIDATE_REPORTER_KEEP; + } else if (GST_PAD_IS_SRC (pad) + && _find_master_report_for_src_pad (pad_monitor, report)) { + gst_object_unref (pad); + return GST_VALIDATE_REPORTER_KEEP; + } + + gst_object_unref (pad); + return GST_VALIDATE_REPORTER_REPORT; +} + +static GstValidateInterceptionReturn +gst_validate_pad_monitor_intercept_report (GstValidateReporter * + reporter, GstValidateReport * report) +{ + GstValidateReporterInterface *iface_class, *old_iface_class; + GstValidatePadMonitor *pad_monitor = GST_VALIDATE_PAD_MONITOR (reporter); + GstValidateReportingDetails monitor_reporting_level; + GstValidateInterceptionReturn ret; + + monitor_reporting_level = + gst_validate_reporter_get_reporting_level (reporter); + + iface_class = + G_TYPE_INSTANCE_GET_INTERFACE (reporter, GST_TYPE_VALIDATE_REPORTER, + GstValidateReporterInterface); + old_iface_class = g_type_interface_peek_parent (iface_class); + + old_iface_class->intercept_report (reporter, report); + + switch (monitor_reporting_level) { + case GST_VALIDATE_SHOW_NONE: + ret = GST_VALIDATE_REPORTER_DROP; + break; + case GST_VALIDATE_SHOW_UNKNOWN: + ret = _concatenate_issues (pad_monitor, report); + break; + default: + ret = GST_VALIDATE_REPORTER_REPORT; + break; + } + + gst_validate_report_set_reporting_level (report, monitor_reporting_level); + return ret; +} + +static void +debug_pending_event (GstPad * pad, GPtrArray * array) +{ + guint i, len; + + len = array->len; + for (i = 0; i < len; i++) { + SerializedEventData *data = g_ptr_array_index (array, i); + GST_DEBUG_OBJECT (pad, "event #%d %" GST_TIME_FORMAT " %s %p", + i, GST_TIME_ARGS (data->timestamp), + GST_EVENT_TYPE_NAME (data->event), data->event); + } +} + +static void +_serialized_event_data_free (SerializedEventData * serialized_event) +{ + gst_event_unref (serialized_event->event); + g_slice_free (SerializedEventData, serialized_event); +} + +static gboolean gst_validate_pad_monitor_do_setup (GstValidateMonitor * + monitor); +static GstElement *gst_validate_pad_monitor_get_element (GstValidateMonitor * + monitor); +static void +gst_validate_pad_monitor_setcaps_pre (GstValidatePadMonitor * pad_monitor, + GstCaps * caps); +static void gst_validate_pad_monitor_setcaps_post (GstValidatePadMonitor * + pad_monitor, GstCaps * caps, gboolean ret); + +#define PAD_IS_IN_PUSH_MODE(p) ((p)->mode == GST_PAD_MODE_PUSH) + +static gboolean +_structure_is_raw_video (GstStructure * structure) +{ + return gst_structure_has_name (structure, "video/x-raw"); +} + +static gboolean +_structure_is_raw_audio (GstStructure * structure) +{ + return gst_structure_has_name (structure, "audio/x-raw"); +} + +static gchar * +_get_event_string (GstEvent * event) +{ + const GstStructure *st; + + if ((st = gst_event_get_structure (event))) + return gst_structure_to_string (st); + else + return g_strdup_printf ("%s", GST_EVENT_TYPE_NAME (event)); +} + +static void +_check_field_type (GstValidatePadMonitor * monitor, + GstStructure * structure, gboolean mandatory, const gchar * field, ...) +{ + va_list var_args; + GType type; + gchar *joined_types = NULL; + const gchar *rejected_types[5]; + gint rejected_types_index = 0; + gchar *struct_str; + + if (!gst_structure_has_field (structure, field)) { + if (mandatory) { + gchar *str = gst_structure_to_string (structure); + + GST_VALIDATE_REPORT (monitor, CAPS_IS_MISSING_FIELD, + "Field '%s' is missing from structure: %s", field, str); + g_free (str); + } else { + GST_DEBUG_OBJECT (monitor, "Field %s is missing but is not mandatory", + field); + } + return; + } + + memset ((gchar **) rejected_types, 0, sizeof (rejected_types)); + va_start (var_args, field); + while ((type = va_arg (var_args, GType)) != 0) { + if (gst_structure_has_field_typed (structure, field, type)) { + va_end (var_args); + return; + } + rejected_types[rejected_types_index++] = g_type_name (type); + } + va_end (var_args); + + joined_types = g_strjoinv (" / ", (gchar **) rejected_types); + struct_str = gst_structure_to_string (structure); + GST_VALIDATE_REPORT (monitor, CAPS_FIELD_HAS_BAD_TYPE, + "Field '%s' has wrong type %s in structure '%s'. Expected: %s", field, + g_type_name (gst_structure_get_field_type (structure, field)), struct_str, + joined_types); + g_free (joined_types); + g_free (struct_str); +} + +static void +gst_validate_pad_monitor_check_raw_video_caps_complete (GstValidatePadMonitor * + monitor, GstStructure * structure) +{ + _check_field_type (monitor, structure, TRUE, "width", G_TYPE_INT, + GST_TYPE_INT_RANGE, 0); + _check_field_type (monitor, structure, TRUE, "height", G_TYPE_INT, + GST_TYPE_INT_RANGE, 0); + _check_field_type (monitor, structure, TRUE, "framerate", GST_TYPE_FRACTION, + GST_TYPE_FRACTION_RANGE, 0); + _check_field_type (monitor, structure, FALSE, "pixel-aspect-ratio", + GST_TYPE_FRACTION, GST_TYPE_FRACTION_RANGE, 0); + _check_field_type (monitor, structure, TRUE, "format", G_TYPE_STRING, + GST_TYPE_LIST); +} + +static void +gst_validate_pad_monitor_check_raw_audio_caps_complete (GstValidatePadMonitor * + monitor, GstStructure * structure) +{ + gint channels; + _check_field_type (monitor, structure, TRUE, "format", G_TYPE_STRING, + GST_TYPE_LIST, 0); + _check_field_type (monitor, structure, TRUE, "layout", G_TYPE_STRING, + GST_TYPE_LIST, 0); + _check_field_type (monitor, structure, TRUE, "rate", G_TYPE_INT, + GST_TYPE_LIST, GST_TYPE_INT_RANGE, 0); + _check_field_type (monitor, structure, TRUE, "channels", G_TYPE_INT, + GST_TYPE_LIST, GST_TYPE_INT_RANGE, 0); + if (gst_structure_get_int (structure, "channels", &channels)) { + if (channels > 2) + _check_field_type (monitor, structure, TRUE, "channel-mask", + GST_TYPE_BITMASK, GST_TYPE_LIST, 0); + } +} + +static void +gst_validate_pad_monitor_check_caps_complete (GstValidatePadMonitor * monitor, + GstCaps * caps) +{ + GstStructure *structure; + gint i; + + GST_DEBUG_OBJECT (monitor, "Checking caps %" GST_PTR_FORMAT, caps); + + for (i = 0; i < gst_caps_get_size (caps); i++) { + structure = gst_caps_get_structure (caps, i); + + if (_structure_is_raw_video (structure)) { + gst_validate_pad_monitor_check_raw_video_caps_complete (monitor, + structure); + + } else if (_structure_is_raw_audio (structure)) { + gst_validate_pad_monitor_check_raw_audio_caps_complete (monitor, + structure); + } + } +} + +static GstCaps * +gst_validate_pad_monitor_get_othercaps (GstValidatePadMonitor * monitor, + GstCaps * filter) +{ + GstCaps *caps = gst_caps_new_empty (); + GstIterator *iter; + gboolean done; + GstPad *otherpad; + GstCaps *peercaps; + GstPad *pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (monitor))); + + iter = gst_pad_iterate_internal_links (pad); + done = FALSE; + while (!done) { + GValue value = { 0, }; + switch (gst_iterator_next (iter, &value)) { + case GST_ITERATOR_OK: + otherpad = g_value_get_object (&value); + + /* TODO What would be the correct caps operation to merge the caps in + * case one sink is internally linked to multiple srcs? */ + peercaps = gst_pad_peer_query_caps (otherpad, filter); + if (peercaps) + caps = gst_caps_merge (caps, peercaps); + + g_value_reset (&value); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + gst_caps_unref (caps); + caps = gst_caps_new_empty (); + break; + case GST_ITERATOR_ERROR: + GST_WARNING_OBJECT (pad, "Internal links pad iteration error"); + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + GST_DEBUG_OBJECT (pad, "Otherpad caps: %" GST_PTR_FORMAT, caps); + + gst_iterator_free (iter); + gst_object_unref (pad); + + return caps; +} + +static gboolean +_structure_is_video (GstStructure * structure) +{ + const gchar *name = gst_structure_get_name (structure); + + return g_strstr_len (name, 6, "video/") + && g_strcmp0 (name, "video/quicktime") != 0; +} + +static gboolean +_structure_is_audio (GstStructure * structure) +{ + const gchar *name = gst_structure_get_name (structure); + + return g_strstr_len (name, 6, "audio/") != NULL; +} + +static gboolean +gst_validate_pad_monitor_pad_should_proxy_othercaps (GstValidatePadMonitor * + monitor) +{ + GstValidateMonitor *parent = GST_VALIDATE_MONITOR_GET_PARENT (monitor); + + if (!parent) + return FALSE; + + /* We only know how to handle othercaps checks for codecs so far */ + return (GST_VALIDATE_ELEMENT_MONITOR_ELEMENT_IS_DECODER (parent) || + GST_VALIDATE_ELEMENT_MONITOR_ELEMENT_IS_ENCODER (parent)) && + !GST_VALIDATE_ELEMENT_MONITOR_ELEMENT_IS_CONVERTER (parent); +} + + +/* Check if the field @f from @s2 (if present) is represented in @s1 + * Represented here means either equal or @s1's value is in a list/range + * from @s2 + */ +static gboolean +_structures_field_is_contained (GstStructure * s1, GstStructure * s2, + gboolean mandatory, const gchar * f) +{ + const GValue *v1; + const GValue *v2; + + v2 = gst_structure_get_value (s2, f); + if (!v2) + return TRUE; /* nothing to compare to */ + + v1 = gst_structure_get_value (s1, f); + if (!v1) + return !mandatory; + + if (!gst_value_is_fixed (v1)) + return TRUE; + + if (gst_value_compare (v1, v2) == GST_VALUE_EQUAL) + return TRUE; + + if (GST_VALUE_HOLDS_LIST (v2)) { + gint i; + for (i = 0; i < gst_value_list_get_size (v2); i++) { + const GValue *v2_subvalue = gst_value_list_get_value (v2, i); + if (gst_value_compare (v1, v2_subvalue) == GST_VALUE_EQUAL) + return TRUE; + } + } + + if (GST_VALUE_HOLDS_ARRAY (v2)) { + gint i; + for (i = 0; i < gst_value_array_get_size (v2); i++) { + const GValue *v2_subvalue = gst_value_array_get_value (v2, i); + if (gst_value_compare (v1, v2_subvalue) == GST_VALUE_EQUAL) + return TRUE; + } + } + + if (GST_VALUE_HOLDS_INT_RANGE (v2)) { + gint min, max; + + min = gst_value_get_int_range_min (v2); + max = gst_value_get_int_range_max (v2); + + if (G_VALUE_HOLDS_INT (v1)) { + gint v = g_value_get_int (v1); + + return v >= min && v <= max; + } else { + /* TODO compare int ranges with int ranges + * or with lists if useful */ + } + } + + if (GST_VALUE_HOLDS_FRACTION_RANGE (v2)) { + const GValue *min, *max; + + min = gst_value_get_fraction_range_min (v2); + max = gst_value_get_fraction_range_max (v2); + + if (GST_VALUE_HOLDS_FRACTION (v1)) { + gint v_min = gst_value_compare (v1, min); + gint v_max = gst_value_compare (v1, max); + + return (v_min == GST_VALUE_EQUAL || v_min == GST_VALUE_GREATER_THAN) && + (v_max == GST_VALUE_EQUAL || v_max == GST_VALUE_LESS_THAN); + } else { + /* TODO compare fraction ranges with fraction ranges + * or with lists if useful */ + } + } + + return FALSE; +} + +static void +_check_and_copy_structure_field (GstStructure * from, GstStructure * to, + const gchar * name) +{ + if (gst_structure_has_field (from, name)) { + gst_structure_set_value (to, name, gst_structure_get_value (from, name)); + } +} + +static GstCaps * +gst_validate_pad_monitor_copy_caps_fields_into_caps (GstValidatePadMonitor * + monitor, GstCaps * from_caps, GstCaps * into_caps) +{ + gint i, j, into_size, from_size; + GstStructure *structure; + GstCaps *res = gst_caps_new_empty (); + + into_size = gst_caps_get_size (into_caps); + from_size = gst_caps_get_size (from_caps); + + for (i = 0; i < into_size; i++) { + GstStructure *s = gst_caps_get_structure (into_caps, i); + + for (j = 0; j < from_size; j++) { + GstStructure *new_structure = gst_structure_copy (s); + + structure = gst_caps_get_structure (from_caps, j); + if (_structure_is_video (structure)) { + _check_and_copy_structure_field (structure, new_structure, "width"); + _check_and_copy_structure_field (structure, new_structure, "height"); + _check_and_copy_structure_field (structure, new_structure, "framerate"); + _check_and_copy_structure_field (structure, new_structure, + "pixel-aspect-ratio"); + } else if (_structure_is_audio (s)) { + _check_and_copy_structure_field (structure, new_structure, "rate"); + _check_and_copy_structure_field (structure, new_structure, "channels"); + } + + gst_caps_append_structure (res, new_structure); + } + } + return res; +} + +static GstCaps * +gst_validate_pad_monitor_transform_caps (GstValidatePadMonitor * monitor, + GstCaps * caps) +{ + GstCaps *othercaps; + GstCaps *new_caps; + GstIterator *iter; + gboolean done; + GstPad *otherpad; + GstCaps *template_caps; + GstPad *pad; + + + GST_DEBUG_OBJECT (monitor, "Transform caps %" GST_PTR_FORMAT, caps); + + if (caps == NULL) + return NULL; + + othercaps = gst_caps_new_empty (); + + pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (monitor))); + iter = gst_pad_iterate_internal_links (pad); + done = FALSE; + while (!done) { + GValue value = { 0, }; + switch (gst_iterator_next (iter, &value)) { + case GST_ITERATOR_OK: + otherpad = g_value_get_object (&value); + template_caps = gst_pad_get_pad_template_caps (otherpad); + + new_caps = + gst_validate_pad_monitor_copy_caps_fields_into_caps (monitor, caps, + template_caps); + if (!gst_caps_is_empty (new_caps)) + gst_caps_append (othercaps, new_caps); + else + gst_caps_unref (new_caps); + + gst_caps_unref (template_caps); + g_value_reset (&value); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + gst_caps_unref (othercaps); + othercaps = gst_caps_new_empty (); + break; + case GST_ITERATOR_ERROR: + GST_WARNING_OBJECT (pad, "Internal links pad iteration error"); + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (iter); + + GST_DEBUG_OBJECT (pad, "Transformed caps: %" GST_PTR_FORMAT, othercaps); + gst_object_unref (pad); + + return othercaps; +} + +static void +gst_validate_pad_monitor_check_caps_fields_proxied (GstValidatePadMonitor * + monitor, GstCaps * caps, GstCaps * filter) +{ + GstStructure *structure; + GstStructure *otherstructure; + GstCaps *othercaps; + GstCaps *otherfilter; + gint i, j; + + if (!gst_validate_pad_monitor_pad_should_proxy_othercaps (monitor)) + return; + + otherfilter = gst_validate_pad_monitor_transform_caps (monitor, filter); + othercaps = gst_validate_pad_monitor_get_othercaps (monitor, otherfilter); + if (otherfilter) + gst_caps_unref (otherfilter); + + for (i = 0; i < gst_caps_get_size (othercaps); i++) { + gboolean found = FALSE; + gboolean type_match = FALSE; + + otherstructure = gst_caps_get_structure (othercaps, i); + + /* look for a proxied version of 'otherstructure' */ + if (_structure_is_video (otherstructure)) { + for (j = 0; j < gst_caps_get_size (caps); j++) { + structure = gst_caps_get_structure (caps, j); + if (_structure_is_video (structure)) { + type_match = TRUE; + if (_structures_field_is_contained (structure, otherstructure, TRUE, + "width") + && _structures_field_is_contained (structure, otherstructure, + TRUE, "height") + && _structures_field_is_contained (structure, otherstructure, + TRUE, "framerate") + && _structures_field_is_contained (structure, otherstructure, + FALSE, "pixel-aspect-ratio")) { + found = TRUE; + break; + } + } + } + } else if (_structure_is_audio (otherstructure)) { + for (j = 0; j < gst_caps_get_size (caps); j++) { + structure = gst_caps_get_structure (caps, j); + if (_structure_is_audio (structure)) { + type_match = TRUE; + if (_structures_field_is_contained (structure, otherstructure, TRUE, + "rate") + && _structures_field_is_contained (structure, otherstructure, + TRUE, "channels")) { + found = TRUE; + break; + } + } + } + } + + if (type_match && !found) { + gchar *otherstruct_str = gst_structure_to_string (otherstructure), + *caps_str = gst_caps_to_string (caps); + + GST_VALIDATE_REPORT (monitor, GET_CAPS_NOT_PROXYING_FIELDS, + "Peer pad structure '%s' has no similar version " + "on pad's caps '%s'", otherstruct_str, caps_str); + + g_free (otherstruct_str); + g_free (caps_str); + } + } + + gst_caps_unref (othercaps); +} + +static void +gst_validate_pad_monitor_check_late_serialized_events (GstValidatePadMonitor * + monitor, GstClockTime ts) +{ + gint i; + GstPad *pad; + + if (!GST_CLOCK_TIME_IS_VALID (ts)) + return; + + pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (monitor))); + + GST_DEBUG_OBJECT (pad, "Timestamp to check %" GST_TIME_FORMAT, + GST_TIME_ARGS (ts)); + + for (i = 0; i < monitor->serialized_events->len; i++) { + SerializedEventData *data = + g_ptr_array_index (monitor->serialized_events, i); + + GST_DEBUG_OBJECT (pad, "Event #%d (%s) ts: %" GST_TIME_FORMAT, + i, GST_EVENT_TYPE_NAME (data->event), GST_TIME_ARGS (data->timestamp)); + + if (GST_CLOCK_TIME_IS_VALID (data->timestamp) && data->timestamp < ts) { + gchar *event_str = _get_event_string (data->event); + + GST_VALIDATE_REPORT (monitor, SERIALIZED_EVENT_WASNT_PUSHED_IN_TIME, + "Serialized event %s wasn't pushed before expected timestamp %" + GST_TIME_FORMAT " on pad %s:%s", event_str, + GST_TIME_ARGS (data->timestamp), GST_DEBUG_PAD_NAME (pad)); + + g_free (event_str); + } else { + /* events should be ordered by ts */ + break; + } + } + + if (i) { + debug_pending_event (pad, monitor->serialized_events); + g_ptr_array_remove_range (monitor->serialized_events, 0, i); + } + + gst_object_unref (pad); +} + +static void +seek_data_free (GstValidatePadSeekData * data) +{ + g_slice_free (GstValidatePadSeekData, data); +} + +static GstValidatePadSeekData * +seek_data_for_seqnum (GstValidatePadMonitor * monitor, guint32 seqnum) +{ + GList *tmp; + + for (tmp = monitor->seeks; tmp; tmp = tmp->next) { + GstValidatePadSeekData *data = (GstValidatePadSeekData *) tmp->data; + if (data->seqnum == seqnum) + return data; + } + + return NULL; +} + +static void +gst_validate_pad_monitor_dispose (GObject * object) +{ + GstValidatePadMonitor *monitor = GST_VALIDATE_PAD_MONITOR_CAST (object); + GstPad *pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (monitor))); + + if (pad) { + if (monitor->pad_probe_id) + gst_pad_remove_probe (pad, monitor->pad_probe_id); + gst_object_unref (pad); + } + + if (monitor->expected_segment) + gst_event_unref (monitor->expected_segment); + + gst_structure_free (monitor->pending_setcaps_fields); + g_ptr_array_unref (monitor->serialized_events); + g_list_free_full (monitor->expired_events, (GDestroyNotify) gst_event_unref); + g_list_free_full (monitor->all_bufs, (GDestroyNotify) gst_buffer_unref); + gst_caps_replace (&monitor->last_caps, NULL); + gst_caps_replace (&monitor->last_query_res, NULL); + gst_caps_replace (&monitor->last_query_filter, NULL); + gst_caps_replace (&monitor->last_refused_caps, NULL); + + g_list_free_full (monitor->seeks, (GDestroyNotify) seek_data_free); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_validate_pad_monitor_class_init (GstValidatePadMonitorClass * klass) +{ + GObjectClass *gobject_class; + GstValidateMonitorClass *monitor_klass; + + gobject_class = G_OBJECT_CLASS (klass); + monitor_klass = GST_VALIDATE_MONITOR_CLASS (klass); + + gobject_class->dispose = gst_validate_pad_monitor_dispose; + + monitor_klass->setup = gst_validate_pad_monitor_do_setup; + monitor_klass->get_element = gst_validate_pad_monitor_get_element; +} + +/* Called when a pad is being flushed */ +static void +gst_validate_pad_monitor_flush (GstValidatePadMonitor * pad_monitor) +{ + /* Note: Keep in the same order as in the GstValidatePadMonitor structure */ + + gst_caps_replace (&pad_monitor->last_caps, NULL); + pad_monitor->caps_is_audio = pad_monitor->caps_is_video = + pad_monitor->caps_is_raw = FALSE; + + pad_monitor->first_buffer = TRUE; + + pad_monitor->has_segment = FALSE; + pad_monitor->is_eos = FALSE; + + pad_monitor->pending_buffer_discont = TRUE; + + gst_event_replace (&pad_monitor->expected_segment, NULL); + if (pad_monitor->serialized_events->len) + g_ptr_array_remove_range (pad_monitor->serialized_events, 0, + pad_monitor->serialized_events->len); + g_list_free_full (pad_monitor->expired_events, + (GDestroyNotify) gst_event_unref); + pad_monitor->expired_events = NULL; + + gst_segment_init (&pad_monitor->segment, GST_FORMAT_BYTES); + pad_monitor->current_timestamp = GST_CLOCK_TIME_NONE; + pad_monitor->current_duration = GST_CLOCK_TIME_NONE; + + pad_monitor->timestamp_range_start = GST_CLOCK_TIME_NONE; + pad_monitor->timestamp_range_end = GST_CLOCK_TIME_NONE; +} + +/* Called when the pad monitor is initialized or when + * the pad is deactivated */ +static void +gst_validate_pad_monitor_reset (GstValidatePadMonitor * pad_monitor) +{ + gst_validate_pad_monitor_flush (pad_monitor); + + /* Note : For the entries that haven't been reset in _flush(), do + * it here and keep in the same order as the GstValidatePadMonitor + * structure */ + + pad_monitor->pending_flush_stop = FALSE; + pad_monitor->pending_newsegment_seqnum = GST_SEQNUM_INVALID; + pad_monitor->pending_eos_seqnum = GST_SEQNUM_INVALID; + + if (pad_monitor->pending_setcaps_fields) + gst_structure_free (pad_monitor->pending_setcaps_fields); + pad_monitor->pending_setcaps_fields = + gst_structure_new_empty (PENDING_FIELDS); + if (pad_monitor->seeks) + g_list_free_full (pad_monitor->seeks, (GDestroyNotify) seek_data_free); + pad_monitor->current_seek = NULL; + pad_monitor->seeks = NULL; + + /* FIXME : Why BYTES and not UNDEFINED ? */ + gst_segment_init (&pad_monitor->segment, GST_FORMAT_BYTES); + + pad_monitor->min_buf_freq = 0; + pad_monitor->buffers_pushed = 0; + pad_monitor->last_buffers_pushed = 0; + pad_monitor->min_buf_freq_interval_ts = GST_CLOCK_TIME_NONE; + pad_monitor->min_buf_freq_first_buffer_ts = GST_CLOCK_TIME_NONE; + pad_monitor->min_buf_freq_start = GST_CLOCK_TIME_NONE; +} + +static void +gst_validate_pad_monitor_init (GstValidatePadMonitor * pad_monitor) +{ + pad_monitor->serialized_events = + g_ptr_array_new_with_free_func ((GDestroyNotify) + _serialized_event_data_free); + + gst_validate_pad_monitor_reset (pad_monitor); +} + +/** + * gst_validate_pad_monitor_new: + * @pad: (transfer none): a #GstPad to run Validate on + */ +GstValidatePadMonitor * +gst_validate_pad_monitor_new (GstPad * pad, GstValidateRunner * runner, + GstValidateElementMonitor * parent) +{ + GstValidatePadMonitor *monitor = g_object_new (GST_TYPE_VALIDATE_PAD_MONITOR, + "object", pad, "validate-runner", runner, "validate-parent", + parent, NULL); + GstObject *target = + gst_validate_monitor_get_target (GST_VALIDATE_MONITOR (monitor)); + + if (target == NULL) { + g_object_unref (monitor); + return NULL; + } + + gst_object_unref (target); + return monitor; +} + +static GstElement * +gst_validate_pad_monitor_get_element (GstValidateMonitor * monitor) +{ + GstPad *pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (monitor))); + GstElement *parent = GST_ELEMENT (gst_pad_get_parent (pad)); + + gst_object_unref (pad); + + return parent; +} + +static void +gst_validate_pad_monitor_event_overrides (GstValidatePadMonitor * pad_monitor, + GstEvent * event) +{ + GList *iter; + + GST_VALIDATE_MONITOR_OVERRIDES_LOCK (pad_monitor); + for (iter = GST_VALIDATE_MONITOR_OVERRIDES (pad_monitor).head; iter; + iter = g_list_next (iter)) { + GstValidateOverride *override = iter->data; + + gst_validate_override_event_handler (override, + GST_VALIDATE_MONITOR_CAST (pad_monitor), event); + } + GST_VALIDATE_MONITOR_OVERRIDES_UNLOCK (pad_monitor); +} + +static void +gst_validate_pad_monitor_buffer_overrides (GstValidatePadMonitor * pad_monitor, + GstBuffer * buffer) +{ + GList *iter; + + GST_VALIDATE_MONITOR_OVERRIDES_LOCK (pad_monitor); + for (iter = GST_VALIDATE_MONITOR_OVERRIDES (pad_monitor).head; iter; + iter = g_list_next (iter)) { + GstValidateOverride *override = iter->data; + + gst_validate_override_buffer_handler (override, + GST_VALIDATE_MONITOR_CAST (pad_monitor), buffer); + } + GST_VALIDATE_MONITOR_OVERRIDES_UNLOCK (pad_monitor); +} + +static void +gst_validate_pad_monitor_buffer_probe_overrides (GstValidatePadMonitor * + pad_monitor, GstBuffer * buffer) +{ + GList *iter; + + GST_VALIDATE_MONITOR_OVERRIDES_LOCK (pad_monitor); + for (iter = GST_VALIDATE_MONITOR_OVERRIDES (pad_monitor).head; iter; + iter = g_list_next (iter)) { + GstValidateOverride *override = iter->data; + + gst_validate_override_buffer_probe_handler (override, + GST_VALIDATE_MONITOR_CAST (pad_monitor), buffer); + } + GST_VALIDATE_MONITOR_OVERRIDES_UNLOCK (pad_monitor); +} + +static void +gst_validate_pad_monitor_query_overrides (GstValidatePadMonitor * pad_monitor, + GstQuery * query) +{ + GList *iter; + + GST_VALIDATE_MONITOR_OVERRIDES_LOCK (pad_monitor); + for (iter = GST_VALIDATE_MONITOR_OVERRIDES (pad_monitor).head; iter; + iter = g_list_next (iter)) { + GstValidateOverride *override = iter->data; + + gst_validate_override_query_handler (override, + GST_VALIDATE_MONITOR_CAST (pad_monitor), query); + } + GST_VALIDATE_MONITOR_OVERRIDES_UNLOCK (pad_monitor); +} + +static void +gst_validate_pad_monitor_setcaps_overrides (GstValidatePadMonitor * pad_monitor, + GstCaps * caps) +{ + GList *iter; + + GST_VALIDATE_MONITOR_OVERRIDES_LOCK (pad_monitor); + for (iter = GST_VALIDATE_MONITOR_OVERRIDES (pad_monitor).head; iter; + iter = g_list_next (iter)) { + GstValidateOverride *override = iter->data; + + gst_validate_override_setcaps_handler (override, + GST_VALIDATE_MONITOR_CAST (pad_monitor), caps); + } + GST_VALIDATE_MONITOR_OVERRIDES_UNLOCK (pad_monitor); +} + +/* FIXME : This is a bit dubious, what's the point of this check ? */ +static gboolean +gst_validate_pad_monitor_timestamp_is_in_received_range (GstValidatePadMonitor * + monitor, GstClockTime ts, GstClockTime tolerance) +{ + GstPad *pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (monitor))); + + GST_DEBUG_OBJECT (pad, + "Checking if timestamp %" GST_TIME_FORMAT " is in range: %" + GST_TIME_FORMAT " - %" GST_TIME_FORMAT " for pad " + "%s:%s with tolerance: %" GST_TIME_FORMAT, GST_TIME_ARGS (ts), + GST_TIME_ARGS (monitor->timestamp_range_start), + GST_TIME_ARGS (monitor->timestamp_range_end), GST_DEBUG_PAD_NAME (pad), + GST_TIME_ARGS (tolerance)); + gst_object_unref (pad); + + return !GST_CLOCK_TIME_IS_VALID (monitor->timestamp_range_start) || + !GST_CLOCK_TIME_IS_VALID (monitor->timestamp_range_end) || + ((monitor->timestamp_range_start >= tolerance ? + monitor->timestamp_range_start - tolerance : 0) <= ts + && (ts >= tolerance ? ts - tolerance : 0) <= + monitor->timestamp_range_end); +} + +/* Iterates over internal links (sinkpads) to check that this buffer has + * a timestamp that is in the range of the lastly received buffers */ +static void + gst_validate_pad_monitor_check_buffer_timestamp_in_received_range + (GstValidatePadMonitor * monitor, GstBuffer * buffer, + GstClockTime tolerance) +{ + GstClockTime ts; + GstClockTime ts_end; + GstIterator *iter; + gboolean has_one = FALSE; + gboolean found = FALSE; + gboolean done; + GstPad *otherpad; + GstValidatePadMonitor *othermonitor; + GstPad *pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (monitor))); + + if (!GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (buffer)) + || !GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DURATION (buffer))) { + GST_DEBUG_OBJECT (pad, + "Can't check buffer timestamps range as " + "buffer has no valid timestamp/duration"); + goto done; + } + + ts = GST_BUFFER_TIMESTAMP (buffer); + ts_end = ts + GST_BUFFER_DURATION (buffer); + + iter = gst_pad_iterate_internal_links (pad); + + if (iter == NULL) { + GST_WARNING_OBJECT (pad, "No iterator available"); + goto done; + } + + done = FALSE; + while (!done) { + GValue value = { 0, }; + switch (gst_iterator_next (iter, &value)) { + case GST_ITERATOR_OK: + otherpad = g_value_get_object (&value); + GST_DEBUG_OBJECT (pad, "Checking pad %s:%s input timestamps", + GST_DEBUG_PAD_NAME (otherpad)); + othermonitor = _GET_PAD_MONITOR (otherpad); + if (!othermonitor) + continue; + + GST_VALIDATE_MONITOR_LOCK (othermonitor); + if (gst_validate_pad_monitor_timestamp_is_in_received_range + (othermonitor, ts, tolerance) + && + gst_validate_pad_monitor_timestamp_is_in_received_range + (othermonitor, ts_end, tolerance)) { + done = TRUE; + found = TRUE; + } + GST_VALIDATE_MONITOR_UNLOCK (othermonitor); + g_value_reset (&value); + has_one = TRUE; + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + has_one = FALSE; + found = FALSE; + break; + case GST_ITERATOR_ERROR: + GST_WARNING_OBJECT (pad, "Internal links pad iteration error"); + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (iter); + + if (!has_one) { + GST_DEBUG_OBJECT (pad, "Skipping timestamp in range check as no " + "internal linked pad was found"); + goto done; + } + if (!found) { + GST_VALIDATE_REPORT (monitor, BUFFER_TIMESTAMP_OUT_OF_RECEIVED_RANGE, + "Timestamp %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT + " is out of range of received input", GST_TIME_ARGS (ts), + GST_TIME_ARGS (ts_end)); + } +done: + if (pad) + gst_object_unref (pad); +} + +static void +gst_validate_pad_monitor_check_discont (GstValidatePadMonitor * pad_monitor, + GstBuffer * buffer) +{ + if (pad_monitor->pending_buffer_discont) { + if (!GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT)) + GST_VALIDATE_REPORT (pad_monitor, BUFFER_MISSING_DISCONT, + "Buffer is missing a DISCONT flag"); + pad_monitor->pending_buffer_discont = FALSE; + } +} + +static void +gst_validate_pad_monitor_check_first_buffer (GstValidatePadMonitor * + pad_monitor, GstBuffer * buffer) +{ + GstPad *pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (pad_monitor))); + + if (G_UNLIKELY (pad_monitor->first_buffer)) { + pad_monitor->first_buffer = FALSE; + + if (!pad_monitor->has_segment && PAD_IS_IN_PUSH_MODE (pad)) { + GST_VALIDATE_REPORT (pad_monitor, BUFFER_BEFORE_SEGMENT, + "Received buffer before Segment event"); + } + + GST_DEBUG_OBJECT (pad, + "Checking first buffer (pts:%" GST_TIME_FORMAT " dts:%" GST_TIME_FORMAT + ")", GST_TIME_ARGS (GST_BUFFER_PTS (buffer)), + GST_TIME_ARGS (GST_BUFFER_DTS (buffer))); + + } + + gst_object_unref (pad); +} + +static void +gst_validate_pad_monitor_check_eos (GstValidatePadMonitor * + pad_monitor, GstBuffer * buffer) +{ + if (G_UNLIKELY (pad_monitor->is_eos)) { + GST_VALIDATE_REPORT (pad_monitor, BUFFER_AFTER_EOS, + "Received buffer %" GST_PTR_FORMAT " after EOS", buffer); + } +} + +static void +gst_validate_pad_monitor_update_buffer_data (GstValidatePadMonitor * + pad_monitor, GstBuffer * buffer) +{ + GstPad *pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (pad_monitor))); + pad_monitor->current_timestamp = GST_BUFFER_TIMESTAMP (buffer); + pad_monitor->current_duration = GST_BUFFER_DURATION (buffer); + if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (buffer))) { + if (GST_CLOCK_TIME_IS_VALID (pad_monitor->timestamp_range_start)) { + pad_monitor->timestamp_range_start = + MIN (pad_monitor->timestamp_range_start, + GST_BUFFER_TIMESTAMP (buffer)); + } else { + pad_monitor->timestamp_range_start = GST_BUFFER_TIMESTAMP (buffer); + } + + if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DURATION (buffer))) { + GstClockTime endts = + GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer); + if (GST_CLOCK_TIME_IS_VALID (pad_monitor->timestamp_range_end)) { + pad_monitor->timestamp_range_end = + MAX (pad_monitor->timestamp_range_end, endts); + } else { + pad_monitor->timestamp_range_end = endts; + } + } + } + GST_DEBUG_OBJECT (pad, "Current stored range: %" GST_TIME_FORMAT + " - %" GST_TIME_FORMAT, + GST_TIME_ARGS (pad_monitor->timestamp_range_start), + GST_TIME_ARGS (pad_monitor->timestamp_range_end)); + + gst_object_unref (pad); +} + +static GstFlowReturn +_combine_flows (GstFlowReturn ret1, GstFlowReturn ret2) +{ + if (ret1 == ret2) + return ret1; + if (ret1 <= GST_FLOW_NOT_NEGOTIATED) + return ret1; + if (ret2 <= GST_FLOW_NOT_NEGOTIATED) + return ret2; + if (ret1 == GST_FLOW_FLUSHING || ret2 == GST_FLOW_FLUSHING) + return GST_FLOW_FLUSHING; + if (ret1 == GST_FLOW_OK || ret2 == GST_FLOW_OK) + return GST_FLOW_OK; + return ret2; +} + +static void +gst_validate_pad_monitor_check_aggregated_return (GstValidatePadMonitor * + monitor, GstObject * parent, GstFlowReturn ret) +{ + GstIterator *iter; + gboolean done; + GstPad *otherpad; + GstPad *peerpad; + GstState state, pending; + GstFlowReturn aggregated = GST_FLOW_NOT_LINKED; + gboolean found_a_pad = FALSE; + GstPad *pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (monitor))); + + iter = gst_pad_iterate_internal_links (pad); + done = FALSE; + while (!done) { + GValue value = { 0, }; + switch (gst_iterator_next (iter, &value)) { + case GST_ITERATOR_OK: + otherpad = g_value_get_object (&value); + peerpad = gst_pad_get_peer (otherpad); + if (peerpad) { + found_a_pad = TRUE; + aggregated = + _combine_flows (aggregated, + gst_pad_get_last_flow_return (peerpad)); + + gst_object_unref (peerpad); + } + g_value_reset (&value); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + break; + case GST_ITERATOR_ERROR: + GST_WARNING_OBJECT (pad, "Internal links pad iteration error"); + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (iter); + if (!found_a_pad) { + /* no peer pad found, nothing to do */ + goto done; + } + + if (aggregated == GST_FLOW_FLUSHING) { + gst_element_get_state (GST_ELEMENT (parent), &state, &pending, 0); + if (state < GST_STATE_PAUSED || pending < GST_STATE_PAUSED) { + /* Aggregated is flushing, we might have been aggregating a combination + * of pads that are not what was present on the element during the actual + * data flow combination (pads might have been removed meanwhile) */ + + goto done; + } + } else if (aggregated == GST_FLOW_OK || aggregated == GST_FLOW_EOS) { + + /* those are acceptable situations */ + if (GST_PAD_IS_FLUSHING (pad) && ret == GST_FLOW_FLUSHING) { + /* pad is flushing, always acceptable to return flushing */ + goto done; + } + + gst_element_get_state (GST_ELEMENT (parent), &state, &pending, 0); + if (ret == GST_FLOW_FLUSHING && (state < GST_STATE_PAUSED + || pending < GST_STATE_PAUSED)) { + /* Element is being teared down, accept FLOW_FLUSHING */ + + goto done; + } + + if (monitor->is_eos && ret == GST_FLOW_EOS) { + /* this element received eos and returned eos */ + goto done; + } + + if (PAD_PARENT_IS_DEMUXER (monitor) && ret == GST_FLOW_EOS) { + /* a demuxer can return EOS when the samples end */ + goto done; + } + } + + if (aggregated != ret) { + GST_VALIDATE_REPORT (monitor, WRONG_FLOW_RETURN, + "Wrong combined flow return %s(%d). Expected: %s(%d)", + gst_flow_get_name (ret), ret, gst_flow_get_name (aggregated), + aggregated); + } + +done: + gst_object_unref (pad); +} + +static void + gst_validate_pad_monitor_otherpad_add_pending_serialized_event + (GstValidatePadMonitor * monitor, GstEvent * event, GstClockTime last_ts) +{ + GstIterator *iter; + gboolean done; + GstPad *otherpad; + GstValidatePadMonitor *othermonitor; + GstPad *pad; + + + if (!GST_EVENT_IS_SERIALIZED (event)) + return; + + pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (monitor))); + iter = gst_pad_iterate_internal_links (pad); + if (iter == NULL) { + /* inputselector will return NULL if the sinkpad is not the active one .... */ + GST_FIXME_OBJECT (pad, "No iterator"); + gst_object_unref (pad); + return; + } + + done = FALSE; + while (!done) { + GValue value = { 0, }; + switch (gst_iterator_next (iter, &value)) { + case GST_ITERATOR_OK: + otherpad = g_value_get_object (&value); + othermonitor = _GET_PAD_MONITOR (otherpad); + if (othermonitor) { + SerializedEventData *data = g_slice_new0 (SerializedEventData); + data->timestamp = last_ts; + data->event = gst_event_ref (event); + GST_VALIDATE_MONITOR_LOCK (othermonitor); + GST_DEBUG_OBJECT (pad, "Storing for pad %s:%s event %p %s", + GST_DEBUG_PAD_NAME (otherpad), event, + GST_EVENT_TYPE_NAME (event)); + g_ptr_array_add (othermonitor->serialized_events, data); + debug_pending_event (otherpad, othermonitor->serialized_events); + GST_VALIDATE_MONITOR_UNLOCK (othermonitor); + } + g_value_reset (&value); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + break; + case GST_ITERATOR_ERROR: + GST_WARNING_OBJECT (pad, "Internal links pad iteration error"); + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (iter); + gst_object_unref (pad); +} + +static void +gst_validate_pad_monitor_otherpad_add_pending_field (GstValidatePadMonitor * + monitor, GstStructure * structure, const gchar * field) +{ + GstIterator *iter; + gboolean done; + GstPad *otherpad; + GstValidatePadMonitor *othermonitor; + const GValue *v; + GstPad *pad; + + v = gst_structure_get_value (structure, field); + pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (monitor))); + + if (v == NULL) { + GST_DEBUG_OBJECT (pad, "Not adding pending field %s as it isn't " + "present on structure %" GST_PTR_FORMAT, field, structure); + gst_object_unref (pad); + return; + } + + iter = gst_pad_iterate_internal_links (pad); + done = FALSE; + while (!done) { + GValue value = { 0, }; + switch (gst_iterator_next (iter, &value)) { + case GST_ITERATOR_OK: + otherpad = g_value_get_object (&value); + othermonitor = _GET_PAD_MONITOR (otherpad); + if (othermonitor) { + GST_VALIDATE_MONITOR_LOCK (othermonitor); + g_assert (othermonitor->pending_setcaps_fields != NULL); + gst_structure_set_value (othermonitor->pending_setcaps_fields, + field, v); + GST_VALIDATE_MONITOR_UNLOCK (othermonitor); + } + g_value_reset (&value); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + break; + case GST_ITERATOR_ERROR: + GST_WARNING_OBJECT (pad, "Internal links pad iteration error"); + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (iter); + gst_object_unref (pad); +} + +static void +gst_validate_pad_monitor_otherpad_clear_pending_fields (GstValidatePadMonitor * + monitor) +{ + GstIterator *iter; + gboolean done; + GstPad *otherpad; + GstValidatePadMonitor *othermonitor; + + GstPad *pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (monitor))); + + iter = gst_pad_iterate_internal_links (pad); + if (iter == NULL) { + gst_object_unref (pad); + GST_DEBUG_OBJECT (monitor, "No internally linked pad"); + + return; + } + + done = FALSE; + while (!done) { + GValue value = { 0, }; + switch (gst_iterator_next (iter, &value)) { + case GST_ITERATOR_OK: + otherpad = g_value_get_object (&value); + othermonitor = _GET_PAD_MONITOR (otherpad); + if (othermonitor) { + GST_VALIDATE_MONITOR_LOCK (othermonitor); + g_assert (othermonitor->pending_setcaps_fields != NULL); + gst_structure_free (othermonitor->pending_setcaps_fields); + othermonitor->pending_setcaps_fields = + gst_structure_new_empty (PENDING_FIELDS); + GST_VALIDATE_MONITOR_UNLOCK (othermonitor); + } + g_value_reset (&value); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + break; + case GST_ITERATOR_ERROR: + GST_WARNING_OBJECT (pad, "Internal links pad iteration error"); + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_object_unref (pad); + gst_iterator_free (iter); +} + +static void +gst_validate_pad_monitor_add_expected_newsegment (GstValidatePadMonitor * + monitor, GstEvent * event) +{ + GstIterator *iter; + gboolean done; + GstPad *otherpad; + GstValidatePadMonitor *othermonitor; + GstPad *pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (monitor))); + + iter = gst_pad_iterate_internal_links (pad); + if (iter == NULL) { + GST_DEBUG_OBJECT (monitor, "No internally linked pad"); + gst_object_unref (pad); + return; + } + + done = FALSE; + while (!done) { + GValue value = { 0, }; + switch (gst_iterator_next (iter, &value)) { + case GST_ITERATOR_OK: + otherpad = g_value_get_object (&value); + if (!otherpad) { + g_value_reset (&value); + continue; + } + + othermonitor = _GET_PAD_MONITOR (otherpad); + if (!othermonitor) { + g_value_reset (&value); + continue; + } + + GST_VALIDATE_MONITOR_LOCK (othermonitor); + gst_event_replace (&othermonitor->expected_segment, event); + GST_VALIDATE_MONITOR_UNLOCK (othermonitor); + g_value_reset (&value); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + break; + case GST_ITERATOR_ERROR: + GST_WARNING_OBJECT (pad, "Internal links pad iteration error"); + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (iter); + gst_object_unref (pad); +} + +/* common checks for both sink and src event functions */ +static void +gst_validate_pad_monitor_common_event_check (GstValidatePadMonitor * + pad_monitor, GstEvent * event) +{ + guint32 seqnum = gst_event_get_seqnum (event); + + if (seqnum == GST_SEQNUM_INVALID) + GST_VALIDATE_REPORT (pad_monitor, EVENT_INVALID_SEQNUM, + "Event %p (%s) has an invalid SEQNUM", event, + GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_START: + { + if (pad_monitor->seeks) { + GstValidatePadSeekData *seekdata = + seek_data_for_seqnum (pad_monitor, seqnum); + + if (!seekdata) + GST_VALIDATE_REPORT (pad_monitor, FLUSH_START_HAS_WRONG_SEQNUM, + "Got: %" G_GUINT32_FORMAT " Expected: %" G_GUINT32_FORMAT, seqnum, + ((GstValidatePadSeekData *) pad_monitor->seeks->data)->seqnum); + else { + if (!(seekdata->flags & GST_SEEK_FLAG_FLUSH)) { + GST_VALIDATE_REPORT (pad_monitor, EVENT_FLUSH_START_UNEXPECTED, + "Received flush-start for a non-flushing seek"); + } + } + } + + if (pad_monitor->pending_flush_stop) { + GST_VALIDATE_REPORT (pad_monitor, EVENT_FLUSH_START_UNEXPECTED, + "Received flush-start from when flush-stop was expected"); + } + pad_monitor->pending_flush_stop = TRUE; + /* Remove the current segment seekdata */ + if (pad_monitor->current_seek) { + pad_monitor->seeks = + g_list_remove (pad_monitor->seeks, pad_monitor->current_seek); + seek_data_free (pad_monitor->current_seek); + pad_monitor->current_seek = NULL; + } + } + break; + case GST_EVENT_FLUSH_STOP: + { + if (pad_monitor->seeks && !seek_data_for_seqnum (pad_monitor, seqnum)) { + GST_VALIDATE_REPORT (pad_monitor, FLUSH_STOP_HAS_WRONG_SEQNUM, + "Got: %" G_GUINT32_FORMAT " Expected: %" G_GUINT32_FORMAT, seqnum, + ((GstValidatePadSeekData *) pad_monitor->seeks->data)->seqnum); + } + + pad_monitor->pending_newsegment_seqnum = seqnum; + pad_monitor->pending_eos_seqnum = seqnum; + + if (!pad_monitor->pending_flush_stop) { + gchar *event_str = _get_event_string (event); + + GST_VALIDATE_REPORT (pad_monitor, EVENT_FLUSH_STOP_UNEXPECTED, + "Unexpected flush-stop %s", event_str); + g_free (event_str); + } + pad_monitor->pending_flush_stop = FALSE; + + /* Buffers following a FLUSH should have the DISCONT flag set */ + pad_monitor->pending_buffer_discont = TRUE; + + /* cleanup our data */ + gst_validate_pad_monitor_flush (pad_monitor); + } + break; + default: + break; + } +} + +static void +mark_pads_eos (GstValidatePadMonitor * pad_monitor) +{ + GstValidatePadMonitor *peer_monitor; + GstPad *real_peer; + GstPad *pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (pad_monitor))); + GstPad *peer = gst_pad_get_peer (pad); + + gst_object_unref (pad); + pad_monitor->is_eos = TRUE; + if (peer) { + real_peer = _get_actual_pad (peer); + peer_monitor = _GET_PAD_MONITOR (real_peer); + if (peer_monitor) + peer_monitor->is_eos = TRUE; + gst_object_unref (peer); + gst_object_unref (real_peer); + } +} + +static inline gboolean +_should_check_buffers (GstValidatePadMonitor * pad_monitor, + gboolean force_checks) +{ + GstPad *pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (pad_monitor))); + GstValidateMonitor *monitor = GST_VALIDATE_MONITOR (pad_monitor); + + if (pad_monitor->first_buffer || force_checks) { + if (pad_monitor->segment.rate != 1.0) { + GST_INFO_OBJECT (pad_monitor, "We do not support buffer checking" + " for trick modes"); + + pad_monitor->check_buffers = FALSE; + } else if (!PAD_PARENT_IS_DECODER (pad_monitor)) { + GST_DEBUG_OBJECT (pad, "Not on a decoder => no buffer checking"); + + pad_monitor->check_buffers = FALSE; + } else if (GST_PAD_DIRECTION (pad) != GST_PAD_SINK) { + GST_DEBUG_OBJECT (pad, "Not a sinkpad => no buffer checking"); + + pad_monitor->check_buffers = FALSE; + } else if (!pad_monitor->caps_is_video) { + GST_DEBUG_OBJECT (pad, "Not working with video => no buffer checking"); + + pad_monitor->check_buffers = FALSE; + } else if (monitor->media_descriptor == NULL) { + GST_DEBUG_OBJECT (pad, "No media_descriptor set => no buffer checking"); + + pad_monitor->check_buffers = FALSE; + } else if (!gst_validate_media_descriptor_detects_frames + (monitor->media_descriptor)) { + GST_DEBUG_OBJECT (pad, + "No frame detection media descriptor => no buffer checking"); + pad_monitor->check_buffers = FALSE; + } else if (pad_monitor->all_bufs == NULL && + !gst_validate_media_descriptor_get_buffers (monitor->media_descriptor, + pad, NULL, &pad_monitor->all_bufs)) { + + GST_INFO_OBJECT (monitor, + "The MediaInfo is marked as detecting frame, but getting frames" + " from pad %" GST_PTR_FORMAT " did not work (some format conversion" + " might be happening)", pad); + + pad_monitor->check_buffers = FALSE; + } else { + if (!pad_monitor->current_buf) + pad_monitor->current_buf = pad_monitor->all_bufs; + pad_monitor->check_buffers = TRUE; + } + } + gst_object_unref (pad); + + return pad_monitor->check_buffers; +} + +static void +gst_validate_monitor_find_next_buffer (GstValidatePadMonitor * pad_monitor) +{ + GList *tmp; + gboolean passed_start = FALSE; + + if (!_should_check_buffers (pad_monitor, TRUE)) + return; + + for (tmp = g_list_last (pad_monitor->all_bufs); tmp; tmp = tmp->prev) { + GstBuffer *cbuf = (GstBuffer *) tmp->data; + GstClockTime ts = + GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DTS (cbuf)) ? GST_BUFFER_DTS (cbuf) + : GST_BUFFER_PTS (cbuf); + + if (!GST_CLOCK_TIME_IS_VALID (ts)) + continue; + + if (ts <= pad_monitor->segment.start) + passed_start = TRUE; + + if (!passed_start) + continue; + + if (!GST_BUFFER_FLAG_IS_SET (cbuf, GST_BUFFER_FLAG_DELTA_UNIT)) { + break; + } + } + + if (tmp == NULL) + pad_monitor->current_buf = pad_monitor->all_bufs; + else + pad_monitor->current_buf = tmp; +} + +static void +post_segment_message (GstValidatePadMonitor * pad_monitor, GstPad * pad, + const GstSegment * segment, guint32 seqnum) +{ + GstValidateMonitor *element_monitor = + GST_VALIDATE_MONITOR_GET_PARENT (pad_monitor); + GstElement *element; + GstStructure *structure; + GstMessage *msg; + + if (element_monitor == NULL) + return; + + element = gst_validate_monitor_get_element (element_monitor); + if (element == NULL) + return; + + GST_DEBUG_OBJECT (pad, + "Posting application message for seqnum:%" G_GUINT32_FORMAT " %" + GST_SEGMENT_FORMAT, seqnum, segment); + + structure = + gst_structure_new ("validate-segment", "segment", GST_TYPE_SEGMENT, + segment, NULL); + msg = gst_message_new_application ((GstObject *) element, structure); + gst_message_set_seqnum (msg, seqnum); + gst_element_post_message (element, msg); + + gst_object_unref (element); + + return; +} + +/* Checks whether a segment is just an update of another, + * That is to say that only the base and offset field differ and all + * other fields are identical */ +static gboolean +is_segment_update (GstSegment * a, const GstSegment * b) +{ + /* Note : We never care about the position field, it is only + * used for internal usage by elements */ + if (a->rate == b->rate && + a->applied_rate == b->applied_rate && + a->format == b->format && a->time == b->time) { + /* Changes in base/offset are considered updates */ + /* Updating the end position of a segment is an update */ + /* Updating the duration of a segment is an update */ + if (a->rate > 0.0) { + if (a->start == b->start) + return TRUE; + } else { + if (a->stop == b->stop) + return TRUE; + } + } + return FALSE; +} + +static GstFlowReturn +gst_validate_pad_monitor_downstream_event_check (GstValidatePadMonitor * + pad_monitor, GstObject * parent, GstEvent * event, + GstPadEventFunction handler) +{ + GstFlowReturn ret = GST_FLOW_OK; + const GstSegment *segment; + guint32 seqnum = gst_event_get_seqnum (event); + GstPad *pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (pad_monitor))); + + gst_validate_pad_monitor_common_event_check (pad_monitor, event); + + /* pre checks */ + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_STREAM_START: + /* Buffers following a STREAM_START should have the DISCONT flag set */ + pad_monitor->pending_buffer_discont = TRUE; + break; + case GST_EVENT_SEGMENT: + { + GstValidatePadSeekData *seekdata = + seek_data_for_seqnum (pad_monitor, seqnum); + + /* parse segment data to be used if event is handled */ + gst_event_parse_segment (event, &segment); + + GST_DEBUG_OBJECT (pad, + "Got segment seqnum:%" G_GUINT32_FORMAT " %" GST_SEGMENT_FORMAT, + seqnum, segment); + + if (pad_monitor->pending_newsegment_seqnum != GST_SEQNUM_INVALID) { + /* FIXME: Convert to more robust checks */ + if (pad_monitor->pending_newsegment_seqnum != seqnum) { + GST_VALIDATE_REPORT (pad_monitor, SEGMENT_HAS_WRONG_SEQNUM, + "Got: %u Expected: %u", seqnum, + pad_monitor->pending_newsegment_seqnum); + } + } + + if (seekdata && seekdata != pad_monitor->current_seek) { + /* Check for accurate seeks */ + if (seekdata->flags & GST_SEEK_FLAG_ACCURATE) { + if (segment->time != seekdata->start) + GST_VALIDATE_REPORT (pad_monitor, SEGMENT_HAS_WRONG_START, + "After an accurate seek, got: %" GST_TIME_FORMAT + " Expected: %" GST_TIME_FORMAT, GST_TIME_ARGS (segment->time), + GST_TIME_ARGS (seekdata->start)); + } + } + + pad_monitor->pending_eos_seqnum = seqnum; + + if (GST_PAD_DIRECTION (pad) == GST_PAD_SINK) { + gst_validate_pad_monitor_add_expected_newsegment (pad_monitor, event); + } else { + /* check if this segment is the expected one */ + if (pad_monitor->expected_segment) { + const GstSegment *exp_segment; + + if (pad_monitor->expected_segment != event) { + gst_event_parse_segment (pad_monitor->expected_segment, + &exp_segment); + if (segment->format == exp_segment->format) { + if ((exp_segment->rate * exp_segment->applied_rate != + segment->rate * segment->applied_rate)) + GST_VALIDATE_REPORT (pad_monitor, EVENT_NEW_SEGMENT_MISMATCH, + "Rate * applied_rate %f != expected %f", + segment->rate * segment->applied_rate, + exp_segment->rate * exp_segment->applied_rate); + if (exp_segment->start != segment->start) + GST_VALIDATE_REPORT (pad_monitor, EVENT_NEW_SEGMENT_MISMATCH, + "Start %" GST_TIME_FORMAT " != expected %" GST_TIME_FORMAT, + GST_TIME_ARGS (segment->start), + GST_TIME_ARGS (exp_segment->start)); + if (exp_segment->stop != segment->stop) + GST_VALIDATE_REPORT (pad_monitor, EVENT_NEW_SEGMENT_MISMATCH, + "Stop %" GST_TIME_FORMAT " != expected %" GST_TIME_FORMAT, + GST_TIME_ARGS (segment->stop), + GST_TIME_ARGS (exp_segment->stop)); + if (exp_segment->position != segment->position) + GST_VALIDATE_REPORT (pad_monitor, EVENT_NEW_SEGMENT_MISMATCH, + "Position %" GST_TIME_FORMAT " != expected %" + GST_TIME_FORMAT, GST_TIME_ARGS (segment->position), + GST_TIME_ARGS (exp_segment->position)); + } + } + gst_event_replace (&pad_monitor->expected_segment, NULL); + } + } + + /* Drop all expected seekdata from before this segment */ + if (seekdata) { + while (pad_monitor->seeks && pad_monitor->seeks->data != seekdata) { + GstValidatePadSeekData *tmp = + (GstValidatePadSeekData *) pad_monitor->seeks->data; + pad_monitor->seeks = + g_list_delete_link (pad_monitor->seeks, pad_monitor->seeks); + seek_data_free (tmp); + } + } + pad_monitor->current_seek = seekdata; + } + break; + case GST_EVENT_CAPS:{ + GstCaps *caps; + + gst_event_parse_caps (event, &caps); + gst_validate_pad_monitor_setcaps_pre (pad_monitor, caps); + break; + } + case GST_EVENT_EOS: + pad_monitor->is_eos = TRUE; + /* FIXME : This feels and looks wrong ... */ + if (pad_monitor->pending_eos_seqnum == GST_SEQNUM_INVALID) { + GST_VALIDATE_REPORT (pad_monitor, EVENT_EOS_WITHOUT_SEGMENT, + "EOS %" GST_PTR_FORMAT " received before a segment was received", + event); + } else if (pad_monitor->pending_eos_seqnum != seqnum) { + GST_VALIDATE_REPORT (pad_monitor, EOS_HAS_WRONG_SEQNUM, + "Got: %u. Expected: %u", seqnum, pad_monitor->pending_eos_seqnum); + } + + /* + * TODO add end of stream checks for + * - events not pushed + * - buffer data not pushed + * - pending events not received + */ + break; + + /* both flushes are handled by the common event function */ + case GST_EVENT_FLUSH_START: + case GST_EVENT_FLUSH_STOP: + case GST_EVENT_TAG: + case GST_EVENT_SINK_MESSAGE: + default: + break; + } + + GST_VALIDATE_MONITOR_UNLOCK (pad_monitor); + GST_VALIDATE_PAD_MONITOR_PARENT_UNLOCK (pad_monitor); + gst_validate_pad_monitor_event_overrides (pad_monitor, event); + if (handler) { + gst_event_ref (event); + if (pad_monitor->event_full_func) + ret = pad_monitor->event_full_func (pad, parent, event); + else if (pad_monitor->event_func (pad, parent, event)) + ret = GST_FLOW_OK; + else + ret = GST_FLOW_ERROR; + } + GST_VALIDATE_PAD_MONITOR_PARENT_LOCK (pad_monitor); + GST_VALIDATE_MONITOR_LOCK (pad_monitor); + + /* post checks */ + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEGMENT: + if (ret == GST_FLOW_OK) { + /* If the new segment is not an update of the previous one, then + * the following buffer should have the DISCONT flag set */ + if (!is_segment_update (&pad_monitor->segment, segment)) + pad_monitor->pending_buffer_discont = TRUE; + if (!pad_monitor->has_segment + && pad_monitor->segment.format != segment->format) { + gst_segment_init (&pad_monitor->segment, segment->format); + } + gst_segment_copy_into (segment, &pad_monitor->segment); + pad_monitor->has_segment = TRUE; + gst_validate_monitor_find_next_buffer (pad_monitor); + if (PAD_PARENT_IS_SINK (pad_monitor)) + post_segment_message (pad_monitor, pad, segment, seqnum); + } + break; + case GST_EVENT_CAPS:{ + GstCaps *caps; + + gst_event_parse_caps (event, &caps); + gst_validate_pad_monitor_setcaps_post (pad_monitor, caps, + ret == GST_FLOW_OK); + break; + } + case GST_EVENT_FLUSH_START: + case GST_EVENT_FLUSH_STOP: + case GST_EVENT_EOS: + case GST_EVENT_TAG: + case GST_EVENT_SINK_MESSAGE: + default: + break; + } + + if (handler) + gst_event_unref (event); + gst_object_unref (pad); + return ret; +} + +static GstValidatePadSeekData * +_store_seek_event_data (GstValidatePadMonitor * pad_monitor, GstEvent * event) +{ + GstValidatePadSeekData *data = g_slice_new0 (GstValidatePadSeekData); + + data->seqnum = gst_event_get_seqnum (event); + gst_event_parse_seek (event, &data->rate, &data->format, &data->flags, + &data->start_type, &data->start, &data->stop_type, &data->stop); + + pad_monitor->seeks = g_list_append (pad_monitor->seeks, data); + + return data; +} + +static gboolean +gst_validate_pad_monitor_src_event_check (GstValidatePadMonitor * pad_monitor, + GstObject * parent, GstEvent * event, GstPadEventFunction handler) +{ + gboolean ret = TRUE; + GstPad *pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (pad_monitor))); + + gst_validate_pad_monitor_common_event_check (pad_monitor, event); + + if (handler) { + GstValidatePadSeekData *seekdata = NULL; + + GST_DEBUG_OBJECT (pad, "event %" GST_PTR_FORMAT, event); + + /* Safely store pending accurate seek values */ + if (GST_EVENT_TYPE (event) == GST_EVENT_SEEK) + seekdata = _store_seek_event_data (pad_monitor, event); + GST_VALIDATE_MONITOR_UNLOCK (pad_monitor); + ret = pad_monitor->event_func (pad, parent, event); + + GST_VALIDATE_MONITOR_LOCK (pad_monitor); + + if (seekdata && !ret) { + /* Remove failed seek from list */ + GST_LOG_OBJECT (pad, "Failed seek, removing stored seek data"); + pad_monitor->seeks = g_list_remove (pad_monitor->seeks, seekdata); + g_slice_free (GstValidatePadSeekData, seekdata); + } + } + + gst_object_unref (pad); + return ret; +} + +static gboolean +gst_validate_pad_monitor_check_right_buffer (GstValidatePadMonitor * + pad_monitor, GstBuffer * buffer) +{ + gchar *checksum; + GstBuffer *wanted_buf; + GstMapInfo map, wanted_map; + + gboolean ret = TRUE; + GstPad *pad; + + + if (_should_check_buffers (pad_monitor, FALSE) == FALSE) + return FALSE; + + pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (pad_monitor))); + if (pad_monitor->current_buf == NULL) { + GST_INFO_OBJECT (pad, "No current buffer one pad, Why?"); + gst_object_unref (pad); + return FALSE; + } + + wanted_buf = pad_monitor->current_buf->data; + + if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_PTS (wanted_buf)) && + GST_CLOCK_TIME_IS_VALID (GST_BUFFER_PTS (buffer)) && + GST_BUFFER_PTS (wanted_buf) != GST_BUFFER_PTS (buffer)) { + + GST_VALIDATE_REPORT (pad_monitor, WRONG_BUFFER, + "buffer %" GST_PTR_FORMAT " PTS %" GST_TIME_FORMAT + " different than expected: %" GST_TIME_FORMAT, buffer, + GST_TIME_ARGS (GST_BUFFER_PTS (buffer)), + GST_TIME_ARGS (GST_BUFFER_PTS (wanted_buf))); + + ret = FALSE; + } + + if (GST_BUFFER_DTS (wanted_buf) != GST_BUFFER_DTS (buffer)) { + GST_VALIDATE_REPORT (pad_monitor, WRONG_BUFFER, + "buffer %" GST_PTR_FORMAT " DTS %" GST_TIME_FORMAT + " different than expected: %" GST_TIME_FORMAT, buffer, + GST_TIME_ARGS (GST_BUFFER_DTS (buffer)), + GST_TIME_ARGS (GST_BUFFER_DTS (wanted_buf))); + ret = FALSE; + } + + if (GST_BUFFER_DURATION (wanted_buf) != GST_BUFFER_DURATION (buffer)) { + GST_VALIDATE_REPORT (pad_monitor, WRONG_BUFFER, + "buffer %" GST_PTR_FORMAT " DURATION %" GST_TIME_FORMAT + " different than expected: %" GST_TIME_FORMAT, buffer, + GST_TIME_ARGS (GST_BUFFER_DURATION (buffer)), + GST_TIME_ARGS (GST_BUFFER_DURATION (wanted_buf))); + ret = FALSE; + } + + if (GST_BUFFER_FLAG_IS_SET (wanted_buf, GST_BUFFER_FLAG_DELTA_UNIT) != + GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT)) { + GST_VALIDATE_REPORT (pad_monitor, WRONG_BUFFER, + "buffer %" GST_PTR_FORMAT " Delta unit is set to %s but expected %s", + buffer, GST_BUFFER_FLAG_IS_SET (buffer, + GST_BUFFER_FLAG_DELTA_UNIT) ? "True" : "False", + GST_BUFFER_FLAG_IS_SET (wanted_buf, + GST_BUFFER_FLAG_DELTA_UNIT) ? "True" : "False"); + ret = FALSE; + } + + g_assert (gst_buffer_map (wanted_buf, &wanted_map, GST_MAP_READ)); + g_assert (gst_buffer_map (buffer, &map, GST_MAP_READ)); + + checksum = g_compute_checksum_for_data (G_CHECKSUM_MD5, + (const guchar *) map.data, map.size); + + if (g_strcmp0 ((gchar *) wanted_map.data, checksum)) { + GST_VALIDATE_REPORT (pad_monitor, WRONG_BUFFER, + "buffer %" GST_PTR_FORMAT " checksum %s different from expected: %s", + buffer, checksum, wanted_map.data); + ret = FALSE; + } + + gst_buffer_unmap (wanted_buf, &wanted_map); + gst_buffer_unmap (buffer, &map); + g_free (checksum); + gst_object_unref (pad); + + pad_monitor->current_buf = pad_monitor->current_buf->next; + + return ret; +} + +static void +gst_validate_pad_monitor_check_return (GstValidatePadMonitor * pad_monitor, + GstFlowReturn ret) +{ + GstValidateMonitor *parent = GST_VALIDATE_MONITOR (pad_monitor); + + if (ret != GST_FLOW_ERROR) + return; + + while (GST_VALIDATE_MONITOR_GET_PARENT (parent)) + parent = GST_VALIDATE_MONITOR_GET_PARENT (parent); + + if (GST_IS_VALIDATE_PIPELINE_MONITOR (parent)) { + GstValidatePipelineMonitor *m = GST_VALIDATE_PIPELINE_MONITOR (parent); + + GST_VALIDATE_MONITOR_LOCK (m); + if (m->got_error == FALSE) { + GST_VALIDATE_REPORT (pad_monitor, FLOW_ERROR_WITHOUT_ERROR_MESSAGE, + "Pad return GST_FLOW_ERROR but no GST_MESSAGE_ERROR was received on" + " the bus"); + + /* Only report it the first time */ + m->got_error = TRUE; + } + GST_VALIDATE_MONITOR_UNLOCK (m); + } +} + +static GstFlowReturn +gst_validate_pad_monitor_chain_func (GstPad * pad, GstObject * parent, + GstBuffer * buffer) +{ + GstValidatePadMonitor *pad_monitor = _GET_PAD_MONITOR (pad); + GstFlowReturn ret; + + GST_VALIDATE_PAD_MONITOR_PARENT_LOCK (pad_monitor); + GST_VALIDATE_MONITOR_LOCK (pad_monitor); + + gst_validate_pad_monitor_check_discont (pad_monitor, buffer); + gst_validate_pad_monitor_check_right_buffer (pad_monitor, buffer); + gst_validate_pad_monitor_check_first_buffer (pad_monitor, buffer); + gst_validate_pad_monitor_update_buffer_data (pad_monitor, buffer); + gst_validate_pad_monitor_check_eos (pad_monitor, buffer); + + GST_VALIDATE_MONITOR_UNLOCK (pad_monitor); + GST_VALIDATE_PAD_MONITOR_PARENT_UNLOCK (pad_monitor); + + gst_validate_pad_monitor_buffer_overrides (pad_monitor, buffer); + + ret = pad_monitor->chain_func (pad, parent, buffer); + + gst_validate_pad_monitor_check_return (pad_monitor, ret); + + GST_VALIDATE_PAD_MONITOR_PARENT_LOCK (pad_monitor); + GST_VALIDATE_MONITOR_LOCK (pad_monitor); + + if (ret == GST_FLOW_EOS) { + mark_pads_eos (pad_monitor); + } + if (PAD_PARENT_IS_DEMUXER (pad_monitor)) + gst_validate_pad_monitor_check_aggregated_return (pad_monitor, parent, ret); + + GST_VALIDATE_MONITOR_UNLOCK (pad_monitor); + GST_VALIDATE_PAD_MONITOR_PARENT_UNLOCK (pad_monitor); + + return ret; +} + +static gboolean +gst_validate_pad_monitor_event_is_tracked (GstValidatePadMonitor * monitor, + GstEvent * event) +{ + if (!GST_EVENT_IS_SERIALIZED (event)) { + return FALSE; + } + + /* we don't track Tag events because they mutate too much and it is hard + * to match a tag event pushed on a source pad with the one that was received + * on a sink pad. + * One idea would be to use seqnum, but it seems that it is undefined whether + * seqnums should be maintained in tag events that are created from others + * up to today. (2013-08-29) + */ + if (GST_EVENT_TYPE (event) == GST_EVENT_TAG) + return FALSE; + + return TRUE; +} + +static GstFlowReturn +gst_validate_pad_monitor_sink_event_full_func (GstPad * pad, GstObject * parent, + GstEvent * event) +{ + GstValidatePadMonitor *pad_monitor = _GET_PAD_MONITOR (pad); + GstFlowReturn ret; + + GST_VALIDATE_PAD_MONITOR_PARENT_LOCK (pad_monitor); + GST_VALIDATE_MONITOR_LOCK (pad_monitor); + + GST_DEBUG_OBJECT (pad, "event %p %s", event, GST_EVENT_TYPE_NAME (event)); + + if (gst_validate_pad_monitor_event_is_tracked (pad_monitor, event)) { + GstClockTime last_ts = GST_CLOCK_TIME_NONE; + if (GST_CLOCK_TIME_IS_VALID (pad_monitor->current_timestamp)) { + last_ts = pad_monitor->current_timestamp; + if (GST_CLOCK_TIME_IS_VALID (pad_monitor->current_duration)) { + last_ts += pad_monitor->current_duration; + } + } + gst_validate_pad_monitor_otherpad_add_pending_serialized_event (pad_monitor, + event, last_ts); + } + + ret = + gst_validate_pad_monitor_downstream_event_check (pad_monitor, parent, + event, pad_monitor->event_func); + + GST_VALIDATE_MONITOR_UNLOCK (pad_monitor); + GST_VALIDATE_PAD_MONITOR_PARENT_UNLOCK (pad_monitor); + return ret; +} + +static gboolean +gst_validate_pad_monitor_sink_event_func (GstPad * pad, GstObject * parent, + GstEvent * event) +{ + if (gst_validate_pad_monitor_sink_event_full_func (pad, parent, + event) == GST_FLOW_OK) + return TRUE; + return FALSE; +} + +static gboolean +gst_validate_pad_monitor_src_event_func (GstPad * pad, GstObject * parent, + GstEvent * event) +{ + GstValidatePadMonitor *pad_monitor = _GET_PAD_MONITOR (pad); + gboolean ret; + + GST_VALIDATE_MONITOR_LOCK (pad_monitor); + ret = gst_validate_pad_monitor_src_event_check (pad_monitor, parent, event, + pad_monitor->event_func); + GST_VALIDATE_MONITOR_UNLOCK (pad_monitor); + return ret; +} + +static gboolean +gst_validate_pad_monitor_query_func (GstPad * pad, GstObject * parent, + GstQuery * query) +{ + GstValidatePadMonitor *pad_monitor = _GET_PAD_MONITOR (pad); + gboolean ret; + + gst_validate_pad_monitor_query_overrides (pad_monitor, query); + ret = pad_monitor->query_func (pad, parent, query); + + if (ret) { + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_ACCEPT_CAPS: + { + gboolean result; + + gst_caps_replace (&pad_monitor->last_refused_caps, NULL); + gst_query_parse_accept_caps_result (query, &result); + if (!result) { + GstCaps *refused_caps; + + gst_query_parse_accept_caps (query, &refused_caps); + pad_monitor->last_refused_caps = gst_caps_copy (refused_caps); + + } + + break; + } + case GST_QUERY_CAPS:{ + GstCaps *res; + GstCaps *filter; + + /* We shouldn't need to lock the parent as this doesn't modify + * other monitors, just does some peer_pad_caps */ + GST_VALIDATE_MONITOR_LOCK (pad_monitor); + + gst_query_parse_caps (query, &filter); + gst_query_parse_caps_result (query, &res); + + gst_caps_replace (&pad_monitor->last_query_res, NULL); + gst_caps_replace (&pad_monitor->last_query_filter, NULL); + pad_monitor->last_query_res = + res ? gst_caps_copy (res) : gst_caps_ref (GST_CAPS_NONE); + pad_monitor->last_query_filter = + filter ? gst_caps_copy (filter) : gst_caps_ref (GST_CAPS_NONE); + + if (GST_PAD_DIRECTION (pad) == GST_PAD_SINK) { + gst_validate_pad_monitor_check_caps_fields_proxied (pad_monitor, res, + filter); + } + GST_VALIDATE_MONITOR_UNLOCK (pad_monitor); + break; + } + default: + break; + } + } + + return ret; +} + +static gboolean +gst_validate_pad_monitor_activatemode_func (GstPad * pad, GstObject * parent, + GstPadMode mode, gboolean active) +{ + GstValidatePadMonitor *pad_monitor = _GET_PAD_MONITOR (pad); + gboolean ret = TRUE; + + /* TODO add overrides for activate func */ + GST_DEBUG_OBJECT (pad, "active:%d", active); + + if (pad_monitor->activatemode_func) + ret = pad_monitor->activatemode_func (pad, parent, mode, active); + if (ret && active == FALSE) { + GST_VALIDATE_MONITOR_LOCK (pad_monitor); + gst_validate_pad_monitor_reset (pad_monitor); + GST_VALIDATE_MONITOR_UNLOCK (pad_monitor); + } + + return ret; +} + +static GstFlowReturn +gst_validate_pad_monitor_get_range_func (GstPad * pad, GstObject * parent, + guint64 offset, guint length, GstBuffer ** buffer) +{ + GstValidatePadMonitor *pad_monitor = _GET_PAD_MONITOR (pad); + + if (pad_monitor->get_range_func) { + GstPad *peer = gst_pad_get_peer (pad); + GstTask *task = NULL; + GThread *thread = NULL; + + if (peer) { + GST_OBJECT_LOCK (peer); + task = GST_PAD_TASK (peer); + if (task && GST_TASK_STATE (task) == GST_TASK_STARTED) { + GST_OBJECT_LOCK (task); + /* Only doing pointer comparison, no need to hold a ref */ + thread = task->thread; + GST_OBJECT_UNLOCK (task); + } + GST_OBJECT_UNLOCK (peer); + + if (thread && thread != g_thread_self ()) { + GST_VALIDATE_REPORT (pad_monitor, PULL_RANGE_FROM_WRONG_THREAD, + "Pulling from wrong thread, expected pad thread: %p, got %p", + task->thread, g_thread_self ()); + } + + gst_object_unref (peer); + } + + return pad_monitor->get_range_func (pad, parent, offset, length, buffer); + } + + return GST_FLOW_NOT_SUPPORTED; + +} + +/* The interval between two buffer frequency checks */ +#define BUF_FREQ_CHECK_INTERVAL (GST_SECOND) + +static void +gst_validate_pad_monitor_check_buffer_freq (GstValidatePadMonitor * monitor, + GstPad * pad) +{ + GstClockTime ts; + + if (!GST_PAD_IS_SRC (pad)) + return; + + if (!monitor->min_buf_freq) + return; + + ts = gst_util_get_timestamp (); + monitor->buffers_pushed++; + + /* Same logic as in fpsdisplaysink to compute the buffer frequency */ + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID + (monitor->min_buf_freq_first_buffer_ts))) { + monitor->min_buf_freq_first_buffer_ts = ts; + monitor->min_buf_freq_interval_ts = ts; + return; + } + + if (GST_CLOCK_DIFF (monitor->min_buf_freq_interval_ts, + ts) > BUF_FREQ_CHECK_INTERVAL) { + guint time_diff; + gdouble fps; + + time_diff = (gdouble) (ts - monitor->min_buf_freq_interval_ts) / GST_SECOND; + fps = + (gdouble) (monitor->buffers_pushed - + monitor->last_buffers_pushed) / time_diff; + + if (fps < monitor->min_buf_freq) { + if (GST_CLOCK_TIME_IS_VALID (monitor->min_buf_freq_start) && + GST_CLOCK_DIFF (monitor->min_buf_freq_first_buffer_ts, + ts) < monitor->min_buf_freq_start) { + GST_DEBUG_OBJECT (pad, + "buffer frequency is too low (%.2f) but ignore for now (buffer-frequency-start =%" + GST_TIME_FORMAT ")", fps, + GST_TIME_ARGS (monitor->min_buf_freq_start)); + } else { + GST_VALIDATE_REPORT (monitor, CONFIG_BUFFER_FREQUENCY_TOO_LOW, + "Buffers are not pushed fast enough on this pad: %.2f/sec (minimum: %.2f)", + fps, monitor->min_buf_freq); + } + } + + monitor->last_buffers_pushed = monitor->buffers_pushed; + monitor->min_buf_freq_interval_ts = ts; + } +} + +static gboolean +gst_validate_pad_monitor_buffer_probe (GstPad * pad, GstBuffer * buffer, + gpointer udata, gboolean pull_mode) +{ + GstValidatePadMonitor *monitor = udata; + + GST_VALIDATE_PAD_MONITOR_PARENT_LOCK (monitor); + GST_VALIDATE_MONITOR_LOCK (monitor); + + if (!pull_mode) + gst_validate_pad_monitor_check_discont (monitor, buffer); + gst_validate_pad_monitor_check_first_buffer (monitor, buffer); + gst_validate_pad_monitor_update_buffer_data (monitor, buffer); + gst_validate_pad_monitor_check_eos (monitor, buffer); + + if (PAD_PARENT_IS_DECODER (monitor) || PAD_PARENT_IS_ENCODER (monitor)) { + GstClockTime tolerance = 0; + + if (monitor->caps_is_audio) + tolerance = AUDIO_TIMESTAMP_TOLERANCE; + + gst_validate_pad_monitor_check_buffer_timestamp_in_received_range (monitor, + buffer, tolerance); + } + + gst_validate_pad_monitor_check_late_serialized_events (monitor, + GST_BUFFER_TIMESTAMP (buffer)); + + /* a GstValidatePadMonitor parent must be a GstValidateElementMonitor */ + if (PAD_PARENT_IS_DECODER (monitor)) { + + /* should not push out of segment data */ + if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (buffer)) && + GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DURATION (buffer)) && + ((!gst_segment_clip (&monitor->segment, monitor->segment.format, + GST_BUFFER_TIMESTAMP (buffer), + GST_BUFFER_TIMESTAMP (buffer) + + GST_BUFFER_DURATION (buffer), NULL, NULL)) || + /* In the case of raw data, buffers should be strictly contained inside the + * segment */ + (monitor->caps_is_raw && + GST_BUFFER_PTS (buffer) + GST_BUFFER_DURATION (buffer) < + monitor->segment.start)) + ) { + /* TODO is this a timestamp issue? */ + GST_VALIDATE_REPORT (monitor, BUFFER_IS_OUT_OF_SEGMENT, + "buffer is out of segment and shouldn't be pushed. Timestamp: %" + GST_TIME_FORMAT " - Duration: %" GST_TIME_FORMAT ". Range: %" + GST_TIME_FORMAT " - %" GST_TIME_FORMAT, + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), + GST_TIME_ARGS (GST_BUFFER_DURATION (buffer)), + GST_TIME_ARGS (monitor->segment.start), + GST_TIME_ARGS (monitor->segment.stop)); + } + } + + gst_validate_pad_monitor_check_buffer_freq (monitor, pad); + + GST_VALIDATE_MONITOR_UNLOCK (monitor); + GST_VALIDATE_PAD_MONITOR_PARENT_UNLOCK (monitor); + gst_validate_pad_monitor_buffer_probe_overrides (monitor, buffer); + return TRUE; +} + +static void +gst_validate_pad_monitor_event_probe (GstPad * pad, GstEvent * event, + gpointer udata) +{ + GstValidatePadMonitor *monitor = GST_VALIDATE_PAD_MONITOR_CAST (udata); + guint32 seqnum = gst_event_get_seqnum (event); + + GST_VALIDATE_PAD_MONITOR_PARENT_LOCK (monitor); + GST_VALIDATE_MONITOR_LOCK (monitor); + + GST_DEBUG_OBJECT (pad, "event %p %s seqnum:%" G_GUINT32_FORMAT, event, + GST_EVENT_TYPE_NAME (event), seqnum); + + if (GST_EVENT_IS_SERIALIZED (event)) { + gint i; + + /* Detect if events the element received are being forwarded in the same order + * + * Several scenarios: + * 1) The element pushes the event as-is + * 2) The element consumes the event and does not forward it + * 3) The element consumes the event and creates another one instead + * 4) The element pushes other serialized event before pushing out the + * one it received + * + * For each pad we have two lists to track serialized events: + * 1) We received on input and expect to see (serialized_events) + * 2) We received on input but don't expect to see (expired_events) + * + * To detect events that are pushed in a different order from the one they were + * received in we check that: + * + * For each event being outputted: + * If it is in the expired_events list: + * RAISE WARNING + * If it is in the serialized_events list: + * If there are other events that were received before: + * Put those events on the expired_events list + * Remove that event and any previous ones from the serialized_events list + * + * Clear expired events list when flushing or on pad deactivation + * + */ + + if (g_list_find (monitor->expired_events, event)) { + gchar *event_str = _get_event_string (event); + /* If it's the expired events, we've failed */ + GST_WARNING_OBJECT (pad, "Did not expect event %p %s", event, + GST_EVENT_TYPE_NAME (event)); + GST_VALIDATE_REPORT (monitor, EVENT_SERIALIZED_OUT_OF_ORDER, + "Serialized event was pushed out of order: %s", event_str); + + g_free (event_str); + monitor->expired_events = g_list_remove (monitor->expired_events, event); + gst_event_unref (event); /* remove the ref that was on the list */ + } else if (monitor->serialized_events->len) { + for (i = 0; i < monitor->serialized_events->len; i++) { + SerializedEventData *next_event = + g_ptr_array_index (monitor->serialized_events, i); + GST_DEBUG_OBJECT (pad, "Checking against stored event #%d: %p %s", i, + next_event->event, GST_EVENT_TYPE_NAME (next_event->event)); + + if (event == next_event->event + || GST_EVENT_TYPE (event) == GST_EVENT_TYPE (next_event->event)) { + /* We have found our event */ + GST_DEBUG_OBJECT (pad, "Found matching event"); + + while (monitor->serialized_events->len > i + && GST_EVENT_TYPE (event) == GST_EVENT_TYPE (next_event->event)) { + /* Swallow all expected events of the same type */ + g_ptr_array_remove_index (monitor->serialized_events, i); + next_event = g_ptr_array_index (monitor->serialized_events, i); + } + + /* Move all previous events to expired events */ + if (G_UNLIKELY (i > 0)) { + GST_DEBUG_OBJECT (pad, + "Moving previous expected events to expired list"); + while (i--) { + next_event = g_ptr_array_index (monitor->serialized_events, 0); + monitor->expired_events = + g_list_append (monitor->expired_events, + gst_event_ref (next_event->event)); + g_ptr_array_remove_index (monitor->serialized_events, 0); + } + } + debug_pending_event (pad, monitor->serialized_events); + break; + } + } + } + } + + /* This so far is just like an event that is flowing downstream, + * so we do the same checks as a sinkpad event handler */ + gst_validate_pad_monitor_downstream_event_check (monitor, NULL, event, NULL); + GST_VALIDATE_MONITOR_UNLOCK (monitor); + GST_VALIDATE_PAD_MONITOR_PARENT_UNLOCK (monitor); +} + +static GstPadProbeReturn +gst_validate_pad_monitor_pad_probe (GstPad * pad, GstPadProbeInfo * info, + gpointer udata) +{ + if (info->type & GST_PAD_PROBE_TYPE_BUFFER) + gst_validate_pad_monitor_buffer_probe (pad, info->data, udata, + GST_PAD_PROBE_INFO_TYPE (info) & GST_PAD_PROBE_TYPE_PULL); + else if (info->type & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM) + gst_validate_pad_monitor_event_probe (pad, info->data, udata); + + return GST_PAD_PROBE_OK; +} + +static void +gst_validate_pad_monitor_update_caps_info (GstValidatePadMonitor * pad_monitor, + GstCaps * caps) +{ + GstStructure *structure; + + g_return_if_fail (gst_caps_is_fixed (caps)); + + pad_monitor->caps_is_audio = FALSE; + pad_monitor->caps_is_video = FALSE; + + structure = gst_caps_get_structure (caps, 0); + if (g_str_has_prefix (gst_structure_get_name (structure), "audio/")) { + pad_monitor->caps_is_audio = TRUE; + } else if (g_str_has_prefix (gst_structure_get_name (structure), "video/")) { + pad_monitor->caps_is_video = TRUE; + } + + if (g_str_has_prefix (gst_structure_get_name (structure), "audio/x-raw") || + g_str_has_prefix (gst_structure_get_name (structure), "video/x-raw")) { + pad_monitor->caps_is_raw = TRUE; + } else { + pad_monitor->caps_is_raw = FALSE; + } +} + +static void +gst_validate_pad_monitor_setcaps_pre (GstValidatePadMonitor * pad_monitor, + GstCaps * caps) +{ + GstStructure *structure; + GstPad *pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (pad_monitor))); + + /* Check if caps are identical to last caps and complain if so + * Only checked for sink pads as src pads might push the same caps + * multiple times during unlinked/autoplugging scenarios */ + if (GST_PAD_IS_SINK (pad) && pad_monitor->last_caps + && gst_caps_is_equal (caps, pad_monitor->last_caps)) { + gchar *caps_str = gst_caps_to_string (caps); + + GST_VALIDATE_REPORT (pad_monitor, EVENT_CAPS_DUPLICATE, "%s", caps_str); + g_free (caps_str); + + } + + gst_validate_pad_monitor_check_caps_complete (pad_monitor, caps); + + if (caps) { + structure = gst_caps_get_structure (caps, 0); + if (gst_structure_n_fields (pad_monitor->pending_setcaps_fields)) { + gint i; + for (i = 0; + i < gst_structure_n_fields (pad_monitor->pending_setcaps_fields); + i++) { + const gchar *name = + gst_structure_nth_field_name (pad_monitor->pending_setcaps_fields, + i); + const GValue *v = gst_structure_get_value (structure, name); + const GValue *otherv = + gst_structure_get_value (pad_monitor->pending_setcaps_fields, name); + + if (v == NULL) { + gchar *caps_str = gst_caps_to_string (caps); + + GST_VALIDATE_REPORT (pad_monitor, CAPS_EXPECTED_FIELD_NOT_FOUND, + "Field %s is missing from setcaps caps '%s'", name, caps_str); + g_free (caps_str); + } else if (gst_value_compare (v, otherv) != GST_VALUE_EQUAL) { + gchar *caps_str = gst_caps_to_string (caps), + *pending_setcaps_fields_str = + gst_structure_to_string (pad_monitor->pending_setcaps_fields); + + + GST_VALIDATE_REPORT (pad_monitor, CAPS_FIELD_UNEXPECTED_VALUE, + "Field %s from setcaps caps '%s' is different " + "from expected value in caps '%s'", name, caps_str, + pending_setcaps_fields_str); + + g_free (pending_setcaps_fields_str); + g_free (caps_str); + } + } + } + + if (GST_PAD_IS_SINK (pad) && + gst_validate_pad_monitor_pad_should_proxy_othercaps (pad_monitor)) { + if (_structure_is_video (structure)) { + GST_DEBUG_OBJECT (pad, + "Adding video common pending fields to other pad: %" GST_PTR_FORMAT, + structure); + gst_validate_pad_monitor_otherpad_add_pending_field (pad_monitor, + structure, "width"); + gst_validate_pad_monitor_otherpad_add_pending_field (pad_monitor, + structure, "height"); + gst_validate_pad_monitor_otherpad_add_pending_field (pad_monitor, + structure, "framerate"); + gst_validate_pad_monitor_otherpad_add_pending_field (pad_monitor, + structure, "pixel-aspect-ratio"); + } else if (_structure_is_audio (structure)) { + GST_DEBUG_OBJECT (pad, + "Adding audio common pending fields to other pad: %" GST_PTR_FORMAT, + structure); + gst_validate_pad_monitor_otherpad_add_pending_field (pad_monitor, + structure, "rate"); + gst_validate_pad_monitor_otherpad_add_pending_field (pad_monitor, + structure, "channels"); + } + } + } + + gst_structure_free (pad_monitor->pending_setcaps_fields); + pad_monitor->pending_setcaps_fields = + gst_structure_new_empty (PENDING_FIELDS); + gst_object_unref (pad); + + gst_validate_pad_monitor_setcaps_overrides (pad_monitor, caps); +} + +static void +gst_validate_pad_monitor_setcaps_post (GstValidatePadMonitor * pad_monitor, + GstCaps * caps, gboolean ret) +{ + if (!ret) + gst_validate_pad_monitor_otherpad_clear_pending_fields (pad_monitor); + else { + if (pad_monitor->last_caps) { + gst_caps_unref (pad_monitor->last_caps); + } + pad_monitor->last_caps = gst_caps_ref (caps); + gst_validate_pad_monitor_update_caps_info (pad_monitor, caps); + } +} + +static void +gst_validate_pad_monitor_get_min_buffer_frequency (GstValidatePadMonitor * + monitor, GstPad * pad) +{ + GList *config, *l; + + if (!GST_PAD_IS_SRC (pad)) + return; + + config = gst_validate_plugin_get_config (NULL); + for (l = config; l != NULL; l = g_list_next (l)) { + GstStructure *s = l->data; + gdouble min_buf_freq; + const gchar *pad_name; + GstElement *element = NULL; + + if (!gst_structure_get_double (s, "min-buffer-frequency", &min_buf_freq)) { + gint max_int; + + if (!gst_structure_get_int (s, "min-buffer-frequency", &max_int)) + goto next; + + min_buf_freq = max_int; + } + + pad_name = gst_structure_get_string (s, "name"); + if (!pad_name) + pad_name = "src"; + + if (g_strcmp0 (GST_PAD_NAME (pad), pad_name)) + goto next; + + element = gst_pad_get_parent_element (pad); + + if (!gst_validate_element_matches_target (element, s)) + goto next; + + monitor->min_buf_freq = min_buf_freq; + + gst_validate_utils_get_clocktime (s, "buffer-frequency-start", + &monitor->min_buf_freq_start); + + GST_DEBUG_OBJECT (pad, "pad has a minimum buffer frequency of %f", + min_buf_freq); + next: + g_clear_object (&element); + } +} + +static gboolean +gst_validate_pad_monitor_do_setup (GstValidateMonitor * monitor) +{ + GstValidatePadMonitor *pad_monitor = GST_VALIDATE_PAD_MONITOR_CAST (monitor); + GstPad *pad = (gpointer) gst_validate_monitor_get_target (monitor); + + if (!GST_IS_PAD (pad)) { + GST_WARNING_OBJECT (monitor, "Trying to create pad monitor with other " + "type of object"); + gst_object_unref (pad); + return FALSE; + } + + if (_GET_PAD_MONITOR (pad)) { + GST_WARNING_OBJECT (pad_monitor, + "Pad already has a validate-monitor associated"); + gst_object_unref (pad); + return FALSE; + } + + _SET_PAD_MONITOR (pad, pad_monitor); + + pad_monitor->event_func = GST_PAD_EVENTFUNC (pad); + pad_monitor->event_full_func = GST_PAD_EVENTFULLFUNC (pad); + pad_monitor->query_func = GST_PAD_QUERYFUNC (pad); + pad_monitor->activatemode_func = GST_PAD_ACTIVATEMODEFUNC (pad); + pad_monitor->get_range_func = GST_PAD_GETRANGEFUNC (pad); + if (GST_PAD_DIRECTION (pad) == GST_PAD_SINK) { + + pad_monitor->chain_func = GST_PAD_CHAINFUNC (pad); + if (pad_monitor->chain_func) + gst_pad_set_chain_function (pad, gst_validate_pad_monitor_chain_func); + + if (pad_monitor->event_full_func) + gst_pad_set_event_full_function (pad, + gst_validate_pad_monitor_sink_event_full_func); + else + gst_pad_set_event_function (pad, + gst_validate_pad_monitor_sink_event_func); + } else { + gst_pad_set_event_function (pad, gst_validate_pad_monitor_src_event_func); + + /* add buffer/event probes */ + pad_monitor->pad_probe_id = + gst_pad_add_probe (pad, + GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | + GST_PAD_PROBE_TYPE_EVENT_FLUSH, + (GstPadProbeCallback) gst_validate_pad_monitor_pad_probe, pad_monitor, + NULL); + } + gst_pad_set_query_function (pad, gst_validate_pad_monitor_query_func); + gst_pad_set_activatemode_function (pad, + gst_validate_pad_monitor_activatemode_func); + + if (GST_PAD_IS_SRC (pad)) { + gst_pad_set_getrange_function (pad, + gst_validate_pad_monitor_get_range_func); + } + + gst_validate_reporter_set_name (GST_VALIDATE_REPORTER (monitor), + g_strdup_printf ("%s:%s", GST_DEBUG_PAD_NAME (pad))); + + if (G_UNLIKELY (GST_PAD_PARENT (pad) == NULL)) + GST_FIXME ("Saw a pad not belonging to any object"); + + gst_validate_pad_monitor_get_min_buffer_frequency (pad_monitor, pad); + + gst_object_unref (pad); + return TRUE; +} diff --git a/validate/gst/validate/gst-validate-pad-monitor.h b/validate/gst/validate/gst-validate-pad-monitor.h new file mode 100644 index 0000000000..22c3479bcb --- /dev/null +++ b/validate/gst/validate/gst-validate-pad-monitor.h @@ -0,0 +1,156 @@ +/* GStreamer + * Copyright (C) 2013 Thiago Santos + * + * gst-validate-pad-monitor.h - Validate PadMonitor class + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_VALIDATE_PAD_MONITOR_H__ +#define __GST_VALIDATE_PAD_MONITOR_H__ + +#include +#include + +typedef struct _GstValidatePadMonitor GstValidatePadMonitor; +typedef struct _GstValidatePadMonitorClass GstValidatePadMonitorClass; +typedef struct _GstValidatePadSeekData GstValidatePadSeekData; + +#include +#include +#include + +G_BEGIN_DECLS + +#ifndef __GI_SCANNER__ +#define GST_TYPE_VALIDATE_PAD_MONITOR (gst_validate_pad_monitor_get_type ()) +#define GST_IS_VALIDATE_PAD_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_VALIDATE_PAD_MONITOR)) +#define GST_IS_VALIDATE_PAD_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_VALIDATE_PAD_MONITOR)) +#define GST_VALIDATE_PAD_MONITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_VALIDATE_PAD_MONITOR, GstValidatePadMonitorClass)) +#define GST_VALIDATE_PAD_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_VALIDATE_PAD_MONITOR, GstValidatePadMonitor)) +#define GST_VALIDATE_PAD_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_VALIDATE_PAD_MONITOR, GstValidatePadMonitorClass)) +#define GST_VALIDATE_PAD_MONITOR_CAST(obj) ((GstValidatePadMonitor*)(obj)) +#define GST_VALIDATE_PAD_MONITOR_CLASS_CAST(klass) ((GstValidatePadMonitorClass*)(klass)) +#endif + +/** + * GstValidatePadMonitor: + * + * GStreamer Validate PadMonitor class. + * + * Class that wraps a #GstPad for Validate checks + */ +struct _GstValidatePadMonitor { + GstValidateMonitor parent; + + gboolean setup; + + GstPadChainFunction chain_func; + GstPadEventFunction event_func; + GstPadEventFullFunction event_full_func; + GstPadQueryFunction query_func; + GstPadActivateModeFunction activatemode_func; + GstPadGetRangeFunction get_range_func; + + gulong pad_probe_id; + + /*< private >*/ + /* Last caps pushed/received */ + GstCaps *last_caps; + gboolean caps_is_audio; + gboolean caps_is_video; + gboolean caps_is_raw; + + /* FIXME : Let's migrate all those booleans into a 32 (or 64) bit flag */ + gboolean first_buffer; + + gboolean has_segment; + gboolean is_eos; + + gboolean pending_flush_stop; + guint32 pending_newsegment_seqnum; + guint32 pending_eos_seqnum; + + /* List of GstValidatePadSeekData containing pending/current seeks */ + GList *seeks; + GstValidatePadSeekData *current_seek; + + /* Whether the next buffer should have a DISCONT flag on it, because + * it's the first one, or follows a SEGMENT and/or a FLUSH */ + gboolean pending_buffer_discont; + + GstEvent *expected_segment; + GPtrArray *serialized_events; + GList *expired_events; + + GstStructure *pending_setcaps_fields; + + GstCaps * last_refused_caps; + GstCaps * last_query_filter; + GstCaps * last_query_res; + + /* tracked data */ + GstSegment segment; + GstClockTime current_timestamp; + GstClockTime current_duration; + + /* Stores the timestamp range of data that has flown through + * this pad by using TIMESTAMP and TIMESTAMP+DURATION from + * incomming buffers. Every time a buffer is pushed, this range + * is extended. + * + * When a buffer is pushed, the timestamp range is checked against + * the outgoing timestamp to check it is in the received boundaries. + */ + GstClockTime timestamp_range_start; + GstClockTime timestamp_range_end; + + /* GstValidateMediaCheck related fields */ + GList *all_bufs; + /* The GstBuffer that should arrive next in a GList */ + GList *current_buf; + gboolean check_buffers; + + /* 'min-buffer-frequency' config check */ + gdouble min_buf_freq; + gint buffers_pushed; + gint last_buffers_pushed; + GstClockTime min_buf_freq_interval_ts; + GstClockTime min_buf_freq_first_buffer_ts; + GstClockTime min_buf_freq_start; +}; + +/** + * GstValidatePadMonitorClass: + * @parent_class: parent + * + * GStreamer Validate PadMonitor object class. + */ +struct _GstValidatePadMonitorClass { + GstValidateMonitorClass parent_class; +}; + +/* normal GObject stuff */ +GST_VALIDATE_API +GType gst_validate_pad_monitor_get_type (void); + +GST_VALIDATE_API +GstValidatePadMonitor * gst_validate_pad_monitor_new (GstPad * pad, GstValidateRunner * runner, GstValidateElementMonitor *element_monitor); + +G_END_DECLS + +#endif /* __GST_VALIDATE_PAD_MONITOR_H__ */ + diff --git a/validate/gst/validate/gst-validate-pipeline-monitor.c b/validate/gst/validate/gst-validate-pipeline-monitor.c new file mode 100644 index 0000000000..5e227207f1 --- /dev/null +++ b/validate/gst/validate/gst-validate-pipeline-monitor.c @@ -0,0 +1,869 @@ +/* GStreamer + * + * Copyright (C) 2014 Thibault Saunier + * + * gst-validate-pipeline-monitor.c - Validate PipelineMonitor class + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gst-validate-internal.h" +#include "gst-validate-pipeline-monitor.h" +#include "gst-validate-pad-monitor.h" +#include "gst-validate-monitor-factory.h" +#include "gst-validate-report.h" +#include "gst-validate-utils.h" +#include "validate.h" + +#define PRINT_POSITION_TIMEOUT 250 + +#ifdef HAVE_UNISTD_H +#include +#endif + +enum +{ + PROP_0, + PROP_VERBOSITY, +}; + +/** + * SECTION:gst-validate-pipeline-monitor + * @title: GstValidatePipelineMonitor + * @short_description: Class that wraps a #GstPipeline for Validate checks + * + * TODO + */ + +typedef struct +{ + gint caps_struct_num; + gint filter_caps_struct_num; + GString *str; + GstStructure *filter; + gboolean found; +} StructureIncompatibleFieldsInfo; + +enum +{ + PROP_LAST +}; + +#define gst_validate_pipeline_monitor_parent_class parent_class +G_DEFINE_TYPE (GstValidatePipelineMonitor, gst_validate_pipeline_monitor, + GST_TYPE_VALIDATE_BIN_MONITOR); + +static void +gst_validate_pipeline_monitor_dispose (GObject * object) +{ + GstValidatePipelineMonitor *self = (GstValidatePipelineMonitor *) object; + + g_clear_object (&self->stream_collection); + if (self->streams_selected) { + g_list_free_full (self->streams_selected, gst_object_unref); + self->streams_selected = NULL; + } + + G_OBJECT_CLASS (gst_validate_pipeline_monitor_parent_class)->dispose (object); +} + +static void +gst_validate_pipeline_monitor_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstElement *pipeline = NULL; + GstValidateMonitor *monitor = GST_VALIDATE_MONITOR_CAST (object); + GstValidatePipelineMonitor *self = GST_VALIDATE_PIPELINE_MONITOR (object); + + switch (prop_id) { + case PROP_VERBOSITY: + pipeline = GST_ELEMENT (gst_validate_monitor_get_pipeline (monitor)); + monitor->verbosity = g_value_get_flags (value); + if (monitor->verbosity & GST_VALIDATE_VERBOSITY_PROPS_CHANGES) { + if (pipeline && !self->deep_notify_id) { + self->deep_notify_id = + gst_element_add_property_deep_notify_watch (pipeline, NULL, TRUE); + } + } else if (pipeline && self->deep_notify_id) { + gst_element_remove_property_notify_watch (pipeline, + self->deep_notify_id); + self->deep_notify_id = 0; + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + gst_clear_object (&pipeline); +} + +static void +gst_validate_pipeline_monitor_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstValidateMonitor *monitor = GST_VALIDATE_MONITOR_CAST (object); + + switch (prop_id) { + case PROP_VERBOSITY: + g_value_set_flags (value, monitor->verbosity); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + +} + +static void +gst_validate_pipeline_monitor_class_init (GstValidatePipelineMonitorClass * + klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gst_validate_pipeline_monitor_dispose; + object_class->set_property = gst_validate_pipeline_monitor_set_property; + object_class->get_property = gst_validate_pipeline_monitor_get_property; + + g_object_class_override_property (object_class, PROP_VERBOSITY, "verbosity"); +} + +static void +gst_validate_pipeline_monitor_init (GstValidatePipelineMonitor * + pipeline_monitor) +{ +} + +static gboolean +print_position (GstValidateMonitor * monitor) +{ + GstQuery *query; + gint64 position, duration; + GstElement *pipeline = + GST_ELEMENT (gst_validate_monitor_get_pipeline (monitor)); + + gdouble rate = 1.0; + GstFormat format = GST_FORMAT_TIME; + + if (!(GST_VALIDATE_MONITOR_CAST (monitor)->verbosity & + GST_VALIDATE_VERBOSITY_POSITION)) + goto done; + + if (!gst_element_query_position (pipeline, format, &position)) { + GST_DEBUG_OBJECT (monitor, "Could not query position"); + + goto done; + } + + format = GST_FORMAT_TIME; + if (!gst_element_query_duration (pipeline, format, &duration)) { + GST_DEBUG_OBJECT (monitor, "Could not query duration"); + + goto done; + } + + if (GST_CLOCK_TIME_IS_VALID (duration) && GST_CLOCK_TIME_IS_VALID (position) + && position > duration) { + GST_VALIDATE_REPORT (monitor, QUERY_POSITION_SUPERIOR_DURATION, + "Reported position %" GST_TIME_FORMAT " > reported duration %" + GST_TIME_FORMAT, GST_TIME_ARGS (position), GST_TIME_ARGS (duration)); + } + + query = gst_query_new_segment (GST_FORMAT_DEFAULT); + if (gst_element_query (pipeline, query)) + gst_query_parse_segment (query, &rate, NULL, NULL, NULL); + gst_query_unref (query); + + gst_validate_print_position (position, duration, rate, NULL); + +done: + gst_object_unref (pipeline); + + return TRUE; +} + +static void +_check_pad_query_failures (GstPad * pad, GString * str, + GstValidatePadMonitor ** last_query_caps_fail_monitor, + GstValidatePadMonitor ** last_refused_caps_monitor) +{ + GstValidatePadMonitor *monitor; + + monitor = g_object_get_data (G_OBJECT (pad), "validate-monitor"); + + if (!monitor) { + GST_DEBUG_OBJECT (pad, "Has no monitor"); + return; + } + + if (monitor->last_query_res && gst_caps_is_empty (monitor->last_query_res)) { + gst_object_replace ((GstObject **) last_query_caps_fail_monitor, + (GstObject *) monitor); + } + + if (monitor->last_refused_caps) + gst_object_replace ((GstObject **) last_refused_caps_monitor, + (GstObject *) monitor); +} + +static GstPad * +_get_peer_pad (GstPad * pad) +{ + GstPad *peer = gst_pad_get_peer (pad); + + if (!peer) + return NULL; + + while (GST_IS_PROXY_PAD (peer)) { + GstPad *next_pad; + + if (GST_PAD_IS_SINK (peer)) { + if (GST_IS_GHOST_PAD (peer)) + next_pad = gst_ghost_pad_get_target (GST_GHOST_PAD (peer)); + else + next_pad = GST_PAD (gst_proxy_pad_get_internal (GST_PROXY_PAD (peer))); + } else { + next_pad = gst_pad_get_peer (peer); + } + + gst_object_unref (peer); + if (!next_pad) + return NULL; + + peer = next_pad; + } + + return peer; +} + +static void +_gather_pad_negotiation_details (GstPad * pad, GString * str, + GstValidatePadMonitor ** last_query_caps_fail_monitor, + GstValidatePadMonitor ** last_refused_caps_monitor) +{ + GList *tmp; + GstElement *next; + GstPad *peer = _get_peer_pad (pad); + + _check_pad_query_failures (pad, str, last_query_caps_fail_monitor, + last_refused_caps_monitor); + + if (!peer) + return; + + _check_pad_query_failures (peer, str, last_query_caps_fail_monitor, + last_refused_caps_monitor); + + next = GST_ELEMENT (gst_pad_get_parent (peer)); + GST_OBJECT_LOCK (next); + for (tmp = next->srcpads; tmp; tmp = tmp->next) { + GstPad *to_check = (GstPad *) tmp->data; + _gather_pad_negotiation_details (to_check, str, + last_query_caps_fail_monitor, last_refused_caps_monitor); + } + GST_OBJECT_UNLOCK (next); + + gst_object_unref (peer); + gst_object_unref (next); +} + +static void +_incompatible_fields_info_set_found (StructureIncompatibleFieldsInfo * info) +{ + if (info->found == FALSE) { + g_string_append_printf (info->str, " for the following possible reasons:"); + info->found = TRUE; + } +} + +static gboolean +_find_structure_incompatible_fields (GQuark field_id, const GValue * value, + StructureIncompatibleFieldsInfo * info) +{ + gchar *value_str, *filter_str; + GValue intersect = { 0, }; + const GValue *filter_value = gst_structure_id_get_value (info->filter, + field_id); + + if (!filter_value) + return TRUE; + + value_str = gst_value_serialize (value); + filter_str = gst_value_serialize (filter_value); + + if (!gst_value_can_intersect (value, filter_value)) { + _incompatible_fields_info_set_found (info); + g_string_append_printf (info->str, + "\n -> Field '%s' downstream value from structure %d '(%s)%s' can't intersect with" + " filter value from structure number %d '(%s)%s' because of their types.", + g_quark_to_string (field_id), info->caps_struct_num, + G_VALUE_TYPE_NAME (value), value_str, info->filter_caps_struct_num, + G_VALUE_TYPE_NAME (filter_value), filter_str); + + return TRUE; + } + + if (gst_value_intersect (&intersect, value, filter_value)) { + g_value_reset (&intersect); + g_free (value_str); + g_free (filter_str); + + return TRUE; + } + + _incompatible_fields_info_set_found (info); + g_string_append_printf (info->str, + "\n -> Field '%s' downstream value from structure %d '(%s)%s' can't intersect with" + " filter value from structure number %d '(%s)%s'", + g_quark_to_string (field_id), info->caps_struct_num, + G_VALUE_TYPE_NAME (value), value_str, info->filter_caps_struct_num, + G_VALUE_TYPE_NAME (filter_value), filter_str); + + g_free (value_str); + g_free (filter_str); + + return TRUE; +} + +static void +_append_query_caps_failure_details (GstValidatePadMonitor * monitor, + GString * str) +{ + gint i, j; + gboolean found = FALSE, empty_filter; + GstCaps *filter = gst_caps_copy (monitor->last_query_filter); + const gchar *filter_name, *possible_name; + GstStructure *filter_struct, *possible_struct; + GstPad *pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (monitor))); + GstCaps *possible_caps = gst_pad_query_caps (pad, NULL); + + g_string_append_printf (str, + "\n Caps negotiation failed starting from pad '%s'" + " as the QUERY_CAPS returned EMPTY caps", + gst_validate_reporter_get_name (GST_VALIDATE_REPORTER (monitor))); + + empty_filter = gst_caps_is_empty (filter); + if (empty_filter) { + GstPad *peer = _get_peer_pad (pad); + gchar *prev_path = NULL; + + if (peer) { + GstObject *prev = gst_pad_get_parent (peer); + if (prev) { + prev_path = gst_object_get_path_string (prev); + gst_object_unref (prev); + } + } + + g_string_append_printf (str, + "\n - The QUERY filter caps is EMPTY, this is invalid and is a bug in " + "a previous element (probably in: '%s')\n", + prev_path ? prev_path : "no suspect"); + g_free (prev_path); + } + + for (i = 0; i < gst_caps_get_size (possible_caps); i++) { + possible_struct = gst_caps_get_structure (possible_caps, i); + possible_name = gst_structure_get_name (possible_struct); + + for (j = 0; j < gst_caps_get_size (filter); j++) { + StructureIncompatibleFieldsInfo info = { + .caps_struct_num = i, + .filter_caps_struct_num = j, + .str = str, + .found = found + }; + + info.filter = filter_struct = gst_caps_get_structure (filter, j); + filter_name = gst_structure_get_name (filter_struct); + + if (g_strcmp0 (possible_name, filter_name)) { + _incompatible_fields_info_set_found (&info); + g_string_append_printf (str, + "\n -> Downstream caps struct %d name '%s' differs from " + "filter caps struct %d name '%s'", + i, possible_name, j, filter_name); + + continue; + } + + gst_structure_foreach (possible_struct, + (GstStructureForeachFunc) _find_structure_incompatible_fields, &info); + + if (info.found) + found = TRUE; + } + } + + if (!found && !empty_filter) { + gchar *filter_caps_str = gst_caps_to_string (filter); + gchar *possible_caps_str = gst_caps_to_string (possible_caps); + + g_string_append_printf (str, + ". The exact reason could not be determined but" + " here is the gathered information:\n" + " - %s last query caps filter: %s\n" + " - %s possible caps (as returned by a query on it without filter): %s\n", + gst_validate_reporter_get_name (GST_VALIDATE_REPORTER (monitor)), + filter_caps_str, + gst_validate_reporter_get_name (GST_VALIDATE_REPORTER (monitor)), + possible_caps_str); + } + + gst_caps_unref (possible_caps); + gst_caps_unref (filter); + gst_object_unref (pad); + +} + +static gboolean +_append_accept_caps_failure_details (GstValidatePadMonitor * monitor, + GString * str) +{ + gint i, j; + GstCaps *refused_caps = gst_caps_copy (monitor->last_refused_caps); + GstPad *pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (monitor))); + GstCaps *possible_caps = gst_pad_query_caps (pad, NULL); + gchar *caps_str = gst_caps_to_string (monitor->last_refused_caps); + StructureIncompatibleFieldsInfo info = { + .str = str, + .found = FALSE + }; + + g_string_append_printf (str, + "\n Caps negotiation failed at pad '%s' as it refused caps: %s", + gst_validate_reporter_get_name (GST_VALIDATE_REPORTER (monitor)), + caps_str); + g_free (caps_str); + + for (i = 0; i < gst_caps_get_size (refused_caps); i++) { + GstStructure *refused_struct = gst_caps_get_structure (refused_caps, i); + const gchar *filter_name; + const gchar *refused_name = gst_structure_get_name (refused_struct); + + for (j = 0; j < gst_caps_get_size (possible_caps); j++) { + info.caps_struct_num = i, + info.filter_caps_struct_num = j, + info.filter = gst_caps_get_structure (possible_caps, j); + + filter_name = gst_structure_get_name (info.filter); + if (g_strcmp0 (refused_name, filter_name)) { + g_string_append_printf (str, + "\n -> Downstream caps struct %d name '%s' differs from " + "filter caps struct %d name '%s'", i, refused_name, j, filter_name); + + continue; + } + + gst_structure_foreach (refused_struct, + (GstStructureForeachFunc) _find_structure_incompatible_fields, &info); + } + } + + gst_caps_unref (possible_caps); + gst_object_unref (pad); + + return TRUE; +} + +static gchar * +_generate_not_negotiated_error_report (GstMessage * msg) +{ + GString *str; + GList *tmp; + GstElement *element = GST_ELEMENT (GST_MESSAGE_SRC (msg)); + GstValidatePadMonitor *last_query_caps_fail_monitor = NULL, + *last_refused_caps_monitor = NULL; + + str = g_string_new (NULL); + g_string_append_printf (str, "Error message posted by: %s", + GST_OBJECT_NAME (element)); + + GST_OBJECT_LOCK (element); + for (tmp = element->srcpads; tmp; tmp = tmp->next) { + GstPad *to_check = (GstPad *) tmp->data; + _gather_pad_negotiation_details (to_check, str, + &last_query_caps_fail_monitor, &last_refused_caps_monitor); + } + GST_OBJECT_UNLOCK (element); + + if (last_query_caps_fail_monitor) + _append_query_caps_failure_details (last_query_caps_fail_monitor, str); + else if (last_refused_caps_monitor) + _append_accept_caps_failure_details (last_refused_caps_monitor, str); + else { + GST_ERROR ("We should always be able to generate a detailed report" + " about why negotiation failed. Please report a bug against" + " gst-devtools:validate with this message and a way to reproduce."); + } + + gst_object_replace ((GstObject **) & last_query_caps_fail_monitor, NULL); + gst_object_replace ((GstObject **) & last_refused_caps_monitor, NULL); + + return g_string_free (str, FALSE); +} + +static void +_bus_handler (GstBus * bus, GstMessage * message, + GstValidatePipelineMonitor * monitor) +{ + GError *err = NULL; + gchar *debug = NULL; + const GstStructure *details = NULL; + gint error_flow = GST_FLOW_OK; + + if (GST_VALIDATE_MONITOR_CAST (monitor)->verbosity & + GST_VALIDATE_VERBOSITY_MESSAGES + && GST_MESSAGE_TYPE (message) != GST_MESSAGE_PROPERTY_NOTIFY) { + GstObject *src_obj; + const GstStructure *s; + guint32 seqnum; + GString *str = g_string_new (NULL); + + seqnum = gst_message_get_seqnum (message); + s = gst_message_get_structure (message); + src_obj = GST_MESSAGE_SRC (message); + + if (GST_IS_ELEMENT (src_obj)) { + g_string_append_printf (str, "Got message #%u from element \"%s\" (%s): ", + (guint) seqnum, GST_ELEMENT_NAME (src_obj), + GST_MESSAGE_TYPE_NAME (message)); + } else if (GST_IS_PAD (src_obj)) { + g_string_append_printf (str, "Got message #%u from pad \"%s:%s\" (%s): ", + (guint) seqnum, GST_DEBUG_PAD_NAME (src_obj), + GST_MESSAGE_TYPE_NAME (message)); + } else if (GST_IS_OBJECT (src_obj)) { + g_string_append_printf (str, "Got message #%u from object \"%s\" (%s): ", + (guint) seqnum, GST_OBJECT_NAME (src_obj), + GST_MESSAGE_TYPE_NAME (message)); + } else { + g_string_append_printf (str, "Got message #%u (%s): ", (guint) seqnum, + GST_MESSAGE_TYPE_NAME (message)); + } + if (s) { + gchar *sstr; + + sstr = gst_structure_to_string (s); + g_string_append_printf (str, "%s\n", sstr); + g_free (sstr); + } else { + g_string_append (str, "no message details\n"); + } + gst_validate_printf (NULL, "%s", str->str); + g_string_free (str, TRUE); + } + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + print_position (GST_VALIDATE_MONITOR (monitor)); + break; + case GST_MESSAGE_ERROR: + gst_message_parse_error (message, &err, &debug); + gst_message_parse_error_details (message, &details); + + if (g_error_matches (err, GST_CORE_ERROR, GST_CORE_ERROR_MISSING_PLUGIN)) { + if (!gst_validate_fail_on_missing_plugin ()) { + gst_validate_skip_test ("missing plugin: %s -- Debug message: %s\n", + err->message, debug); + } else { + GST_VALIDATE_REPORT (monitor, MISSING_PLUGIN, + "Error: %s -- Debug message: %s", err->message, debug); + } + } else if ((g_error_matches (err, GST_STREAM_ERROR, + GST_STREAM_ERROR_FAILED) && details + && gst_structure_get_int (details, "flow-return", &error_flow) + && error_flow == GST_FLOW_NOT_NEGOTIATED) + || g_error_matches (err, GST_STREAM_ERROR, GST_STREAM_ERROR_FORMAT)) { + gchar *report = _generate_not_negotiated_error_report (message); + + GST_VALIDATE_REPORT (monitor, NOT_NEGOTIATED, "%s", report); + g_free (report); + } else { + GST_VALIDATE_REPORT (monitor, ERROR_ON_BUS, + "Got error: %s -- Debug message: %s (%" GST_PTR_FORMAT ")", + err->message, debug, details); + } + + GST_VALIDATE_MONITOR_LOCK (monitor); + monitor->got_error = TRUE; + GST_VALIDATE_MONITOR_UNLOCK (monitor); + g_error_free (err); + g_free (debug); + break; + case GST_MESSAGE_WARNING: + gst_message_parse_warning (message, &err, &debug); + GST_VALIDATE_REPORT (monitor, WARNING_ON_BUS, + "Got warning: %s -- Debug message: %s", err->message, debug); + g_error_free (err); + g_free (debug); + break; + case GST_MESSAGE_STATE_CHANGED: + { + GstObject *target = + gst_validate_monitor_get_target (GST_VALIDATE_MONITOR (monitor)); + if (GST_MESSAGE_SRC (message) == target) { + GstState oldstate, newstate, pending; + + gst_message_parse_state_changed (message, &oldstate, &newstate, + &pending); + + if (oldstate == GST_STATE_READY && newstate == GST_STATE_PAUSED) { + monitor->print_pos_srcid = + g_timeout_add (PRINT_POSITION_TIMEOUT, + (GSourceFunc) print_position, monitor); + } else if (oldstate >= GST_STATE_PAUSED && newstate <= GST_STATE_READY) { + if (monitor->print_pos_srcid + && g_source_remove (monitor->print_pos_srcid)) + monitor->print_pos_srcid = 0; + monitor->got_error = FALSE; + } + } + + if (target) + gst_object_unref (target); + + break; + } + case GST_MESSAGE_BUFFERING: + { + JsonBuilder *jbuilder = json_builder_new (); + GstBufferingMode mode; + gint percent; + + gst_message_parse_buffering (message, &percent); + gst_message_parse_buffering_stats (message, &mode, NULL, NULL, NULL); + + json_builder_begin_object (jbuilder); + json_builder_set_member_name (jbuilder, "type"); + json_builder_add_string_value (jbuilder, "buffering"); + json_builder_set_member_name (jbuilder, "state"); + if (percent == 100) { + /* a 100% message means buffering is done */ + gst_validate_printf (NULL, "\nDone buffering\n"); + json_builder_add_string_value (jbuilder, "done"); + if (monitor->buffering) { + monitor->print_pos_srcid = + g_timeout_add (PRINT_POSITION_TIMEOUT, + (GSourceFunc) print_position, monitor); + monitor->buffering = FALSE; + } + } else { + /* buffering... */ + if (!monitor->buffering) { + monitor->buffering = TRUE; + gst_validate_printf (NULL, "\nStart buffering\n"); + json_builder_add_string_value (jbuilder, "started"); + if (monitor->print_pos_srcid + && g_source_remove (monitor->print_pos_srcid)) { + monitor->print_pos_srcid = 0; + } + } else { + json_builder_add_string_value (jbuilder, "progress"); + } + if (is_tty ()) + gst_validate_printf (NULL, "%s %d%% \r", "Buffering...", percent); + } + json_builder_set_member_name (jbuilder, "position"); + json_builder_add_int_value (jbuilder, percent); + json_builder_end_object (jbuilder); + + gst_validate_send (json_builder_get_root (jbuilder)); + g_object_unref (jbuilder); + break; + } + case GST_MESSAGE_STREAM_COLLECTION: + { + GstStreamCollection *collection = NULL; + gst_message_parse_stream_collection (message, &collection); + gst_object_replace ((GstObject **) & monitor->stream_collection, + (GstObject *) collection); + gst_object_unref (collection); + break; + } + case GST_MESSAGE_STREAMS_SELECTED: + { + guint i; + + if (monitor->streams_selected) { + g_list_free_full (monitor->streams_selected, gst_object_unref); + monitor->streams_selected = NULL; + } + + for (i = 0; i < gst_message_streams_selected_get_size (message); i++) { + GstStream *stream = + gst_message_streams_selected_get_stream (message, i); + + monitor->streams_selected = + g_list_append (monitor->streams_selected, stream); + } + break; + } + case GST_MESSAGE_PROPERTY_NOTIFY: + { + const GValue *val; + const gchar *name; + GstObject *obj; + gchar *val_str = NULL; + gchar *obj_name; + + if (!(GST_VALIDATE_MONITOR_CAST (monitor)->verbosity & + GST_VALIDATE_VERBOSITY_PROPS_CHANGES)) + return; + + gst_message_parse_property_notify (message, &obj, &name, &val); + + obj_name = gst_object_get_path_string (GST_OBJECT (obj)); + if (val != NULL) { + if (G_VALUE_HOLDS_STRING (val)) + val_str = g_value_dup_string (val); + else if (G_VALUE_TYPE (val) == GST_TYPE_CAPS) + val_str = gst_caps_to_string (g_value_get_boxed (val)); + else if (G_VALUE_TYPE (val) == GST_TYPE_TAG_LIST) + val_str = gst_tag_list_to_string (g_value_get_boxed (val)); + else if (G_VALUE_TYPE (val) == GST_TYPE_STRUCTURE) + val_str = gst_structure_to_string (g_value_get_boxed (val)); + else + val_str = gst_value_serialize (val); + } else { + val_str = g_strdup ("(no value)"); + } + + gst_validate_printf (NULL, "%s: %s = %s\n", obj_name, name, val_str); + g_free (obj_name); + g_free (val_str); + break; + + break; + } + default: + break; + } +} + +static void +gst_validate_pipeline_monitor_create_scenarios (GstValidateBinMonitor * monitor) +{ + /* scenarios currently only make sense for pipelines */ + const gchar *scenarios_names, *scenario_name = NULL; + gchar **scenarios = NULL, *testfile = NULL; + GstObject *target = + gst_validate_monitor_get_target (GST_VALIDATE_MONITOR (monitor)); + GstValidateRunner *runner = + gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (monitor)); + GList *scenario_structs = NULL; + + if (gst_validate_get_test_file_scenario (&scenario_structs, &scenario_name, + &testfile)) { + if (scenario_name) { + monitor->scenario = + gst_validate_scenario_factory_create (runner, + GST_ELEMENT_CAST (target), scenario_name); + goto done; + } + + monitor->scenario = + gst_validate_scenario_from_structs (runner, + GST_ELEMENT_CAST (target), scenario_structs, testfile); + + goto done; + } + + if ((scenarios_names = g_getenv ("GST_VALIDATE_SCENARIO"))) { + gint i; + + scenarios = g_strsplit (scenarios_names, G_SEARCHPATH_SEPARATOR_S, 0); + for (i = 0; scenarios[i]; i++) { + gchar **scenario_v = g_strsplit (scenarios[i], "->", 2); + + if (scenario_v[1] && target) { + if (!g_pattern_match_simple (scenario_v[1], GST_OBJECT_NAME (target))) { + GST_INFO_OBJECT (monitor, "Not attaching to pipeline %" GST_PTR_FORMAT + " as not matching pattern %s", target, scenario_v[1]); + + g_strfreev (scenario_v); + goto done; + } + } + if (target) + monitor->scenario = + gst_validate_scenario_factory_create (runner, + GST_ELEMENT_CAST (target), scenario_v[0]); + else + GST_INFO_OBJECT (monitor, "Not creating scenario as monitor" + " already does not have a target."); + g_strfreev (scenario_v); + } + } +done: + g_strfreev (scenarios); + if (target) + gst_object_unref (target); + if (runner) + gst_object_unref (runner); +} + +/** + * gst_validate_pipeline_monitor_new: + * @pipeline: (transfer none): a #GstPipeline to run Validate on + */ +GstValidatePipelineMonitor * +gst_validate_pipeline_monitor_new (GstPipeline * pipeline, + GstValidateRunner * runner, GstValidateMonitor * parent) +{ + GstBus *bus; + GstValidatePipelineMonitor *monitor = + g_object_new (GST_TYPE_VALIDATE_PIPELINE_MONITOR, "object", + pipeline, "validate-runner", runner, "validate-parent", parent, + "pipeline", pipeline, NULL); + GstObject *target = + gst_validate_monitor_get_target (GST_VALIDATE_MONITOR (monitor)); + + if (target == NULL) { + g_object_unref (monitor); + return NULL; + } + + gst_validate_pipeline_monitor_create_scenarios (GST_VALIDATE_BIN_MONITOR + (monitor)); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + gst_bus_enable_sync_message_emission (bus); + g_signal_connect (bus, "sync-message", (GCallback) _bus_handler, monitor); + + if (GST_VALIDATE_MONITOR_CAST (monitor)->verbosity & + GST_VALIDATE_VERBOSITY_PROPS_CHANGES) { + monitor->deep_notify_id = + gst_element_add_property_deep_notify_watch ((GstElement *) pipeline, + NULL, TRUE); + } + + gst_object_unref (bus); + + if (g_strcmp0 (G_OBJECT_TYPE_NAME (pipeline), "GstPlayBin") == 0) + monitor->is_playbin = TRUE; + else if (g_strcmp0 (G_OBJECT_TYPE_NAME (pipeline), "GstPlayBin3") == 0) + monitor->is_playbin3 = TRUE; + gst_object_unref (target); + + return monitor; +} diff --git a/validate/gst/validate/gst-validate-pipeline-monitor.h b/validate/gst/validate/gst-validate-pipeline-monitor.h new file mode 100644 index 0000000000..37c88bcc57 --- /dev/null +++ b/validate/gst/validate/gst-validate-pipeline-monitor.h @@ -0,0 +1,98 @@ +/* GStreamer + * Copyright (C) 2014 Thibault Saunier + * + * gst-validate-pipeline-monitor.h - Validate PipelineMonitor class + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_VALIDATE_PIPELINE_MONITOR_H__ +#define __GST_VALIDATE_PIPELINE_MONITOR_H__ + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#ifndef __GI_SCANNER__ +#define GST_TYPE_VALIDATE_PIPELINE_MONITOR (gst_validate_pipeline_monitor_get_type ()) +#define GST_IS_VALIDATE_PIPELINE_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_VALIDATE_PIPELINE_MONITOR)) +#define GST_IS_VALIDATE_PIPELINE_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_VALIDATE_PIPELINE_MONITOR)) +#define GST_VALIDATE_PIPELINE_MONITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_VALIDATE_PIPELINE_MONITOR, GstValidatePipelineMonitorClass)) +#define GST_VALIDATE_PIPELINE_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_VALIDATE_PIPELINE_MONITOR, GstValidatePipelineMonitor)) +#define GST_VALIDATE_PIPELINE_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_VALIDATE_PIPELINE_MONITOR, GstValidatePipelineMonitorClass)) +#define GST_VALIDATE_PIPELINE_MONITOR_CAST(obj) ((GstValidatePipelineMonitor*)(obj)) +#define GST_VALIDATE_PIPELINE_MONITOR_CLASS_CAST(klass) ((GstValidatePipelineMonitorClass*)(klass)) +#endif + +#define GST_VALIDATE_PIPELINE_MONITOR_GET_PIPELINE(m) (GST_PIPELINE_CAST (GST_VALIDATE_ELEMENT_MONITOR_GET_ELEMENT (m))) + +typedef struct _GstValidatePipelineMonitor GstValidatePipelineMonitor; +typedef struct _GstValidatePipelineMonitorClass GstValidatePipelineMonitorClass; + +/** + * GstValidatePipelineMonitor: + * + * GStreamer Validate PipelineMonitor class. + * + * Class that wraps a #GstPipeline for Validate checks + */ +struct _GstValidatePipelineMonitor { + GstValidateBinMonitor parent; + + /*< private >*/ + gulong element_added_id; + guint print_pos_srcid; + gboolean buffering; + gboolean got_error; + + /* TRUE if monitoring a playbin2 pipeline */ + gboolean is_playbin; + /* TRUE if monitoring a playbin3 pipeline */ + gboolean is_playbin3; + + /* Latest collection received from GST_MESSAGE_STREAM_COLLECTION */ + GstStreamCollection *stream_collection; + /* Latest GstStream received from GST_MESSAGE_STREAMS_SELECTED */ + GList *streams_selected; + + gulong deep_notify_id; +}; + +/** + * GstValidatePipelineMonitorClass: + * @parent_class: parent + * + * GStreamer Validate PipelineMonitor object class. + */ +struct _GstValidatePipelineMonitorClass { + GstValidateBinMonitorClass parent_class; +}; + +/* normal GObject stuff */ +GST_VALIDATE_API +GType gst_validate_pipeline_monitor_get_type (void); + +GST_VALIDATE_API +GstValidatePipelineMonitor * gst_validate_pipeline_monitor_new (GstPipeline * pipeline, + GstValidateRunner * runner, GstValidateMonitor * parent); + +G_END_DECLS + +#endif /* __GST_VALIDATE_PIPELINE_MONITOR_H__ */ + diff --git a/validate/gst/validate/gst-validate-report.c b/validate/gst/validate/gst-validate-report.c new file mode 100644 index 0000000000..0dcff9e3e6 --- /dev/null +++ b/validate/gst/validate/gst-validate-report.c @@ -0,0 +1,1474 @@ +/* GStreamer + * + * Copyright (C) 2013 Collabora Ltd. + * Author: Thiago Sousa Santos + * + * gst-validate-monitor-report.c - Validate report/issues functions + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + +#include /* exit */ +#include /* fprintf */ +#include +#include + +#include +#include "gst-validate-i18n-lib.h" +#include "gst-validate-internal.h" + +#include "gst-validate-report.h" +#include "gst-validate-reporter.h" +#include "gst-validate-monitor.h" +#include "gst-validate-scenario.h" + +static GstClockTime _gst_validate_report_start_time = 0; +static GstValidateDebugFlags _gst_validate_flags = 0; +static GHashTable *_gst_validate_issues = NULL; +static FILE **log_files = NULL; +static gboolean output_is_tty = TRUE; + +/* Tcp server for communications with gst-validate-launcher */ +GSocketClient *socket_client = NULL; +GSocketConnection *server_connection = NULL; +GOutputStream *server_ostream = NULL; + +static GType _gst_validate_report_type = 0; + +static JsonNode * +gst_validate_report_serialize (GstValidateReport * report) +{ + JsonNode *node = json_node_alloc (); + JsonObject *jreport = json_object_new (); + + json_object_set_string_member (jreport, "type", "report"); + json_object_set_string_member (jreport, "issue-id", + g_quark_to_string (report->issue->issue_id)); + json_object_set_string_member (jreport, "summary", report->issue->summary); + json_object_set_string_member (jreport, "level", + gst_validate_report_level_get_name (report->level)); + json_object_set_string_member (jreport, "detected-on", report->reporter_name); + json_object_set_string_member (jreport, "details", report->message); + + node = json_node_init_object (node, jreport); + json_object_unref (jreport); + + return node; +} + +GType +gst_validate_report_get_type (void) +{ + if (_gst_validate_report_type == 0) { + _gst_validate_report_type = + g_boxed_type_register_static (g_intern_static_string + ("GstValidateReport"), (GBoxedCopyFunc) gst_mini_object_ref, + (GBoxedFreeFunc) gst_mini_object_unref); + + json_boxed_register_serialize_func (_gst_validate_report_type, + JSON_NODE_OBJECT, + (JsonBoxedSerializeFunc) gst_validate_report_serialize); + } + + return _gst_validate_report_type; +} + + +GRegex *newline_regex = NULL; + +GST_DEBUG_CATEGORY_STATIC (gst_validate_report_debug); +#undef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT gst_validate_report_debug + +#define GST_VALIDATE_REPORT_SHADOW_REPORTS_LOCK(r) \ + G_STMT_START { \ + (g_mutex_lock (&((GstValidateReport *) r)->shadow_reports_lock)); \ + } G_STMT_END + +#define GST_VALIDATE_REPORT_SHADOW_REPORTS_UNLOCK(r) \ + G_STMT_START { \ + (g_mutex_unlock (&((GstValidateReport *) r)->shadow_reports_lock)); \ + } G_STMT_END + +static GstValidateIssue * +gst_validate_issue_ref (GstValidateIssue * issue) +{ + g_return_val_if_fail (issue != NULL, NULL); + + g_atomic_int_inc (&issue->refcount); + + return issue; +} + +static void +gst_validate_issue_unref (GstValidateIssue * issue) +{ + if (G_UNLIKELY (g_atomic_int_dec_and_test (&issue->refcount))) { + g_free (issue->summary); + g_free (issue->description); + + /* We are using an string array for area and name */ + g_strfreev (&issue->area); + + g_slice_free (GstValidateIssue, issue); + } +} + + +G_DEFINE_BOXED_TYPE (GstValidateIssue, gst_validate_issue, + (GBoxedCopyFunc) gst_validate_issue_ref, + (GBoxedFreeFunc) gst_validate_issue_unref); + +GstValidateIssueId +gst_validate_issue_get_id (GstValidateIssue * issue) +{ + return issue->issue_id; +} + +/** + * gst_validate_issue_new_full: + * @issue_id: The ID of the issue, should be a GQuark + * @summary: A summary of the issue + * @description: A more complete description of the issue + * @default_level: The level at which the issue will be reported by default + * @flags: The flags to determine behaviour of the issue + * + * Returns: (transfer full): The newly created #GstValidateIssue + */ +GstValidateIssue * +gst_validate_issue_new_full (GstValidateIssueId issue_id, const gchar * summary, + const gchar * description, GstValidateReportLevel default_level, + GstValidateIssueFlags flags) +{ + GstValidateIssue *issue; + gchar **area_name = g_strsplit (g_quark_to_string (issue_id), "::", 2); + + if (!(area_name[0] != NULL && area_name[1] != NULL && area_name[2] == NULL)) { + g_warning ("Wrong issue ID: %s (should be in the form: area::name)", + g_quark_to_string (issue_id)); + g_strfreev (area_name); + + return NULL; + } + + issue = g_slice_new (GstValidateIssue); + issue->issue_id = issue_id; + issue->summary = g_strdup (summary); + issue->description = g_strdup (description); + issue->default_level = default_level; + issue->area = area_name[0]; + issue->name = area_name[1]; + issue->flags = flags; + + g_free (area_name); + return issue; +} + +/** + * gst_validate_issue_new: + * @issue_id: The ID of the issue, should be a GQuark + * @summary: A summary of the issue + * @description: A more complete description of the issue + * @default_level: The level at which the issue will be reported by default + * + * Returns: (transfer full): The newly created #GstValidateIssue + */ +GstValidateIssue * +gst_validate_issue_new (GstValidateIssueId issue_id, const gchar * summary, + const gchar * description, GstValidateReportLevel default_level) +{ + GstValidateIssue *issue; + gchar **area_name = g_strsplit (g_quark_to_string (issue_id), "::", 2); + + if (!(area_name[0] != NULL && area_name[1] != NULL && area_name[2] == NULL)) { + g_warning ("Wrong issue ID: %s (should be in the form: area::name)", + g_quark_to_string (issue_id)); + g_strfreev (area_name); + + return NULL; + } + + issue = g_slice_new (GstValidateIssue); + issue->issue_id = issue_id; + issue->summary = g_strdup (summary); + issue->description = g_strdup (description); + issue->default_level = default_level; + issue->area = area_name[0]; + issue->name = area_name[1]; + issue->flags = GST_VALIDATE_ISSUE_FLAGS_NONE; + + g_free (area_name); + return issue; +} + +void +gst_validate_issue_set_default_level (GstValidateIssue * issue, + GstValidateReportLevel default_level) +{ + GST_INFO ("Setting issue %s::%s default level to %s", + issue->area, issue->name, + gst_validate_report_level_get_name (default_level)); + + issue->default_level = default_level; +} + +/** + * gst_validate_issue_register: + * @issue: (transfer none): The #GstValidateIssue to register + * + * Registers @issue in the issue type system + */ +void +gst_validate_issue_register (GstValidateIssue * issue) +{ + g_return_if_fail (g_hash_table_lookup (_gst_validate_issues, + (gpointer) gst_validate_issue_get_id (issue)) == NULL); + + g_hash_table_insert (_gst_validate_issues, + (gpointer) gst_validate_issue_get_id (issue), issue); +} + +#define REGISTER_VALIDATE_ISSUE(lvl,id,sum,desc) \ + gst_validate_issue_register (gst_validate_issue_new (id, \ + sum, desc, GST_VALIDATE_REPORT_LEVEL_##lvl)) + +#define REGISTER_VALIDATE_ISSUE_FULL(lvl,id,sum,desc,flags) \ + gst_validate_issue_register (gst_validate_issue_new_full (id, \ + sum, desc, GST_VALIDATE_REPORT_LEVEL_##lvl, flags)) +static void +gst_validate_report_load_issues (void) +{ + g_return_if_fail (_gst_validate_issues == NULL); + + _gst_validate_issues = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify) gst_validate_issue_unref); + + /* ** + * WARNING: The `summary` is used to define known issues in the testsuites. + * Avoid changing them or **make sure** to at least update the validate test + * suite if you do so. + * **/ + REGISTER_VALIDATE_ISSUE (WARNING, BUFFER_BEFORE_SEGMENT, + "buffer was received before a segment", + _("in push mode, a segment event must be received before a buffer")); + REGISTER_VALIDATE_ISSUE (ISSUE, BUFFER_IS_OUT_OF_SEGMENT, + "buffer is out of the segment range", + _("buffer being pushed is out of the current segment's start-stop " + "range. Meaning it is going to be discarded downstream without " + "any use")); + REGISTER_VALIDATE_ISSUE (WARNING, BUFFER_TIMESTAMP_OUT_OF_RECEIVED_RANGE, + "buffer timestamp is out of the received buffer timestamps' range", + _("a buffer leaving an element should have its timestamps in the range " + "of the received buffers timestamps. i.e. If an element received " + "buffers with timestamps from 0s to 10s, it can't push a buffer with " + "a 11s timestamp, because it doesn't have data for that")); + REGISTER_VALIDATE_ISSUE (WARNING, WRONG_BUFFER, + "Received buffer does not correspond to wanted one.", + _("When checking playback of a file against a MediaInfo file" + " all buffers coming into the decoders might be checked" + " and should have the exact expected metadatas and hash of the" + " content")); + REGISTER_VALIDATE_ISSUE (CRITICAL, WRONG_FLOW_RETURN, + "flow return from pad push doesn't match expected value", + _("flow return from a 1:1 sink/src pad element is as simple as " + "returning what downstream returned. For elements that have multiple " + "src pads, flow returns should be properly combined")); + REGISTER_VALIDATE_ISSUE (ISSUE, BUFFER_AFTER_EOS, + "buffer was received after EOS", + _("a pad shouldn't receive any more buffers after it gets EOS")); + REGISTER_VALIDATE_ISSUE (WARNING, FLOW_ERROR_WITHOUT_ERROR_MESSAGE, + "GST_FLOW_ERROR returned without posting an ERROR on the bus", + _("Element MUST post a GST_MESSAGE_ERROR with GST_ELEMENT_ERROR before" + " returning GST_FLOW_ERROR")); + REGISTER_VALIDATE_ISSUE (WARNING, BUFFER_MISSING_DISCONT, + _("Buffer didn't have expected DISCONT flag"), + _("Buffers after SEGMENT and FLUSH must have a DISCONT flag")); + + REGISTER_VALIDATE_ISSUE (ISSUE, CAPS_IS_MISSING_FIELD, + "caps is missing a required field for its type", + _("some caps types are expected to contain a set of basic fields. " + "For example, raw video should have 'width', 'height', 'framerate' " + "and 'pixel-aspect-ratio'")); + REGISTER_VALIDATE_ISSUE (WARNING, CAPS_FIELD_HAS_BAD_TYPE, + "caps field has an unexpected type", + _("some common caps fields should always use the same expected types")); + REGISTER_VALIDATE_ISSUE (WARNING, CAPS_EXPECTED_FIELD_NOT_FOUND, + "caps expected field wasn't present", + _("a field that should be present in the caps wasn't found. " + "Fields sets on a sink pad caps should be propagated downstream " + "when it makes sense to do so")); + REGISTER_VALIDATE_ISSUE (CRITICAL, GET_CAPS_NOT_PROXYING_FIELDS, + "getcaps function isn't proxying downstream fields correctly", + _("elements should set downstream caps restrictions on its caps when " + "replying upstream's getcaps queries to avoid upstream sending data" + " in an unsupported format")); + REGISTER_VALIDATE_ISSUE (CRITICAL, CAPS_FIELD_UNEXPECTED_VALUE, + "a field in caps has an unexpected value", + _("fields set on a sink pad should be propagated downstream via " + "set caps")); + + REGISTER_VALIDATE_ISSUE (WARNING, EVENT_NEWSEGMENT_NOT_PUSHED, + "new segment event wasn't propagated downstream", + _("segments received from upstream should be pushed downstream")); + REGISTER_VALIDATE_ISSUE (WARNING, SERIALIZED_EVENT_WASNT_PUSHED_IN_TIME, + "a serialized event received should be pushed in the same 'time' " + "as it was received", + _("serialized events should be pushed in the same order they are " + "received and serialized with buffers. If an event is received after" + " a buffer with timestamp end 'X', it should be pushed right after " + "buffers with timestamp end 'X'")); + REGISTER_VALIDATE_ISSUE (ISSUE, EOS_HAS_WRONG_SEQNUM, + "EOS events that are part of the same pipeline 'operation' should " + "have the same seqnum", + _("when events/messages are created from another event/message, " + "they should have their seqnums set to the original event/message " + "seqnum")); + REGISTER_VALIDATE_ISSUE (ISSUE, FLUSH_START_HAS_WRONG_SEQNUM, + "FLUSH_START events that are part of the same pipeline 'operation' should " + "have the same seqnum", + _("when events/messages are created from another event/message, " + "they should have their seqnums set to the original event/message " + "seqnum")); + REGISTER_VALIDATE_ISSUE (ISSUE, FLUSH_STOP_HAS_WRONG_SEQNUM, + "FLUSH_STOP events that are part of the same pipeline 'operation' should " + "have the same seqnum", + _("when events/messages are created from another event/message, " + "they should have their seqnums set to the original event/message " + "seqnum")); + REGISTER_VALIDATE_ISSUE (ISSUE, SEGMENT_HAS_WRONG_SEQNUM, + "SEGMENT events that are part of the same pipeline 'operation' should " + "have the same seqnum", + _("when events/messages are created from another event/message, " + "they should have their seqnums set to the original event/message " + "seqnum")); + REGISTER_VALIDATE_ISSUE (CRITICAL, SEGMENT_HAS_WRONG_START, + "A segment doesn't have the proper time value after an ACCURATE seek", + _("If a seek with the ACCURATE flag was accepted, the following segment " + "should have a time value corresponding exactly to the requested start " + "seek time")); + REGISTER_VALIDATE_ISSUE (WARNING, EVENT_SERIALIZED_OUT_OF_ORDER, + "a serialized event received should be pushed in the same order " + "as it was received", + _("serialized events should be pushed in the same order they are " + "received.")); + REGISTER_VALIDATE_ISSUE (WARNING, EVENT_NEW_SEGMENT_MISMATCH, + "a new segment event has different value than the received one", + _("when receiving a new segment, an element should push an equivalent " + "segment downstream")); + REGISTER_VALIDATE_ISSUE (WARNING, EVENT_FLUSH_START_UNEXPECTED, + "received an unexpected flush start event", NULL); + REGISTER_VALIDATE_ISSUE (WARNING, EVENT_FLUSH_STOP_UNEXPECTED, + "received an unexpected flush stop event", NULL); + REGISTER_VALIDATE_ISSUE (WARNING, EVENT_CAPS_DUPLICATE, + "received the same caps twice", NULL); + + REGISTER_VALIDATE_ISSUE (CRITICAL, EVENT_SEEK_NOT_HANDLED, + "seek event wasn't handled", NULL); + REGISTER_VALIDATE_ISSUE (CRITICAL, EVENT_SEEK_RESULT_POSITION_WRONG, + "position after a seek is wrong", NULL); + REGISTER_VALIDATE_ISSUE (CRITICAL, EVENT_SEEK_INVALID_SEQNUM, + "segments after a seek don't have the same seqnum", NULL); + + REGISTER_VALIDATE_ISSUE (WARNING, EVENT_EOS_WITHOUT_SEGMENT, + "EOS received without segment event before", + _("A segment event should always be sent before data flow" + " EOS being some kind of data flow, there is no exception" + " in that regard")); + + REGISTER_VALIDATE_ISSUE (CRITICAL, EVENT_INVALID_SEQNUM, + "Event has an invalid seqnum", + _("An event is using GST_SEQNUM_INVALID. This should never happen")); + + REGISTER_VALIDATE_ISSUE (CRITICAL, STATE_CHANGE_FAILURE, + "state change failed", NULL); + + REGISTER_VALIDATE_ISSUE (WARNING, FILE_SIZE_INCORRECT, + "resulting file size wasn't within the expected values", NULL); + REGISTER_VALIDATE_ISSUE (WARNING, FILE_DURATION_INCORRECT, + "resulting file duration wasn't within the expected values", NULL); + REGISTER_VALIDATE_ISSUE (WARNING, FILE_SEEKABLE_INCORRECT, + "resulting file wasn't seekable or not seekable as expected", NULL); + REGISTER_VALIDATE_ISSUE (CRITICAL, FILE_PROFILE_INCORRECT, + "resulting file stream profiles didn't match expected values", NULL); + REGISTER_VALIDATE_ISSUE (ISSUE, FILE_TAG_DETECTION_INCORRECT, + "detected tags are different than expected ones", NULL); + REGISTER_VALIDATE_ISSUE (CRITICAL, FILE_FRAMES_INCORRECT, + "resulting file frames are not as expected", NULL); + REGISTER_VALIDATE_ISSUE (CRITICAL, FILE_SEGMENT_INCORRECT, + "resulting segment is not as expected", NULL); + REGISTER_VALIDATE_ISSUE (WARNING, FILE_NO_STREAM_INFO, + "the discoverer could not determine the stream info", NULL); + REGISTER_VALIDATE_ISSUE (WARNING, FILE_NO_STREAM_ID, + "the discoverer found a stream that had no stream ID", NULL); + + + REGISTER_VALIDATE_ISSUE (CRITICAL, ALLOCATION_FAILURE, + "a memory allocation failed during Validate run", NULL); + REGISTER_VALIDATE_ISSUE (CRITICAL, MISSING_PLUGIN, + "a gstreamer plugin is missing and prevented Validate from running", + NULL); + REGISTER_VALIDATE_ISSUE (CRITICAL, NOT_NEGOTIATED, + "a NOT NEGOTIATED message has been posted on the bus.", NULL); + REGISTER_VALIDATE_ISSUE (WARNING, WARNING_ON_BUS, + "We got a WARNING message on the bus", NULL); + REGISTER_VALIDATE_ISSUE (CRITICAL, ERROR_ON_BUS, + "We got an ERROR message on the bus", NULL); + REGISTER_VALIDATE_ISSUE (WARNING, QUERY_POSITION_SUPERIOR_DURATION, + "Query position reported a value superior than what query duration " + "returned", NULL); + REGISTER_VALIDATE_ISSUE (WARNING, QUERY_POSITION_OUT_OF_SEGMENT, + "Query position reported a value outside of the current expected " + "segment", NULL); + REGISTER_VALIDATE_ISSUE (CRITICAL, SCENARIO_NOT_ENDED, + "The program stopped before some actions were executed", NULL); + REGISTER_VALIDATE_ISSUE (CRITICAL, SCENARIO_ACTION_TIMEOUT, + "The execution of an action timed out", NULL); + REGISTER_VALIDATE_ISSUE (CRITICAL, SCENARIO_FILE_MALFORMED, + "The scenario file was malformed", NULL); + REGISTER_VALIDATE_ISSUE_FULL (CRITICAL, SCENARIO_ACTION_EXECUTION_ERROR, + "The execution of an action did not properly happen", NULL, + GST_VALIDATE_ISSUE_FLAGS_NO_BACKTRACE | + GST_VALIDATE_ISSUE_FLAGS_FULL_DETAILS); + REGISTER_VALIDATE_ISSUE_FULL (CRITICAL, SCENARIO_ACTION_CHECK_ERROR, + "A check action failed", NULL, + GST_VALIDATE_ISSUE_FLAGS_NO_BACKTRACE | + GST_VALIDATE_ISSUE_FLAGS_FULL_DETAILS); + REGISTER_VALIDATE_ISSUE (ISSUE, SCENARIO_ACTION_EXECUTION_ISSUE, + "An issue happened during the execution of a scenario", NULL); + REGISTER_VALIDATE_ISSUE (CRITICAL, CONFIG_LATENCY_TOO_HIGH, + "The pipeline latency is higher than the maximum allowed by the scenario", + NULL); + REGISTER_VALIDATE_ISSUE (CRITICAL, CONFIG_TOO_MANY_BUFFERS_DROPPED, + "The number of dropped buffers is higher than the maximum allowed by the scenario", + NULL); + REGISTER_VALIDATE_ISSUE (CRITICAL, CONFIG_BUFFER_FREQUENCY_TOO_LOW, + _ + ("Pad buffers push frequency is lower than the minimum required by the config"), + NULL); + REGISTER_VALIDATE_ISSUE_FULL (WARNING, G_LOG_WARNING, + _("We got a g_log warning"), NULL, + GST_VALIDATE_ISSUE_FLAGS_FORCE_BACKTRACE | + GST_VALIDATE_ISSUE_FLAGS_FULL_DETAILS); + REGISTER_VALIDATE_ISSUE_FULL (CRITICAL, G_LOG_CRITICAL, + "We got a g_log critical issue", NULL, + GST_VALIDATE_ISSUE_FLAGS_FORCE_BACKTRACE | + GST_VALIDATE_ISSUE_FLAGS_FULL_DETAILS); + REGISTER_VALIDATE_ISSUE_FULL (ISSUE, G_LOG_ISSUE, "We got a g_log issue", + NULL, + GST_VALIDATE_ISSUE_FLAGS_FORCE_BACKTRACE | + GST_VALIDATE_ISSUE_FLAGS_FULL_DETAILS); + + REGISTER_VALIDATE_ISSUE (CRITICAL, PULL_RANGE_FROM_WRONG_THREAD, + "gst_pad_pull_range called from wrong thread", + _("gst_pad_pull_range has to be called from the sinkpad task thread.")); +} + +gboolean +gst_validate_send (JsonNode * root) +{ + gboolean res = FALSE; + JsonGenerator *jgen; + gsize message_length; + gchar *object, *message; + GError *error = NULL; + + if (!server_ostream) + goto done; + + jgen = json_generator_new (); + json_generator_set_root (jgen, root); + + object = json_generator_to_data (jgen, &message_length); + message = g_malloc0 (message_length + 5); + GST_WRITE_UINT32_BE (message, message_length); + strcpy (&message[4], object); + g_free (object); + + res = g_output_stream_write_all (server_ostream, message, message_length + 4, + NULL, NULL, &error); + + if (!res) { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PENDING)) { + GST_DEBUG ("Stream was busy, trying again later."); + + g_free (message); + g_object_unref (jgen); + if (error) + g_error_free (error); + g_idle_add ((GSourceFunc) gst_validate_send, root); + return G_SOURCE_REMOVE; + } + + GST_ERROR ("ERROR: Can't write to remote: %s", error->message); + } else if (!g_output_stream_flush (server_ostream, NULL, &error)) { + GST_ERROR ("ERROR: Can't flush stream: %s", error->message); + } + + g_free (message); + g_object_unref (jgen); + if (error) + g_error_free (error); + +done: + json_node_free (root); + + return G_SOURCE_REMOVE; +} + +void +gst_validate_report_init (void) +{ + const gchar *var, *file_env, *server_env, *uuid; + const GDebugKey keys[] = { + {"fatal_criticals", GST_VALIDATE_FATAL_CRITICALS}, + {"fatal_warnings", GST_VALIDATE_FATAL_WARNINGS}, + {"fatal_issues", GST_VALIDATE_FATAL_ISSUES}, + {"print_issues", GST_VALIDATE_PRINT_ISSUES}, + {"print_warnings", GST_VALIDATE_PRINT_WARNINGS}, + {"print_criticals", GST_VALIDATE_PRINT_CRITICALS} + }; + + GST_DEBUG_CATEGORY_INIT (gst_validate_report_debug, "gstvalidatereport", + GST_DEBUG_FG_YELLOW, "Gst validate reporting"); + + _gst_validate_report_type = gst_validate_report_get_type (); + + if (_gst_validate_report_start_time == 0) { + _gst_validate_report_start_time = gst_util_get_timestamp (); + + /* init the debug flags */ + var = g_getenv ("GST_VALIDATE"); + if (var && strlen (var) > 0) { + _gst_validate_flags = + g_parse_debug_string (var, keys, G_N_ELEMENTS (keys)); + } + + gst_validate_report_load_issues (); + } +#ifdef HAVE_UNISTD_H + output_is_tty = isatty (1); +#endif + + server_env = g_getenv ("GST_VALIDATE_SERVER"); + uuid = g_getenv ("GST_VALIDATE_UUID"); + + if (server_env && !uuid) { + GST_INFO ("No GST_VALIDATE_UUID specified !"); + } else if (server_env) { + GstUri *server_uri = gst_uri_from_string (server_env); + + if (server_uri && !g_strcmp0 (gst_uri_get_scheme (server_uri), "tcp")) { + JsonBuilder *jbuilder; + GError *err = NULL; + socket_client = g_socket_client_new (); + + server_connection = g_socket_client_connect_to_host (socket_client, + gst_uri_get_host (server_uri), gst_uri_get_port (server_uri), + NULL, &err); + + if (!server_connection) { + g_clear_error (&err); + g_clear_object (&socket_client); + + } else { + server_ostream = + g_io_stream_get_output_stream (G_IO_STREAM (server_connection)); + jbuilder = json_builder_new (); + json_builder_begin_object (jbuilder); + json_builder_set_member_name (jbuilder, "uuid"); + json_builder_add_string_value (jbuilder, uuid); + json_builder_set_member_name (jbuilder, "started"); + json_builder_add_boolean_value (jbuilder, TRUE); + json_builder_end_object (jbuilder); + + gst_validate_send (json_builder_get_root (jbuilder)); + g_object_unref (jbuilder); + } + + gst_uri_unref (server_uri); + } else { + GST_ERROR ("Server URI not valid: %s", server_env); + } + } + + file_env = g_getenv ("GST_VALIDATE_FILE"); + if (file_env != NULL && *file_env != '\0') { + gint i; + gchar **wanted_files; + wanted_files = g_strsplit (file_env, G_SEARCHPATH_SEPARATOR_S, 0); + + /* FIXME: Make sure it is freed in the deinit function when that is + * implemented */ + log_files = + g_malloc0 (sizeof (FILE *) * (g_strv_length (wanted_files) + 1)); + for (i = 0; i < g_strv_length (wanted_files); i++) { + FILE *log_file; + if (g_strcmp0 (wanted_files[i], "stderr") == 0) { + log_file = stderr; + } else if (g_strcmp0 (wanted_files[i], "stdout") == 0) { + log_file = stdout; + } else { + log_file = g_fopen (wanted_files[i], "w"); + } + + if (log_file == NULL) { + g_printerr ("Could not open log file '%s' for writing: %s\n", file_env, + g_strerror (errno)); + log_file = stderr; + } + + log_files[i] = log_file; + } + + g_strfreev (wanted_files); + } else { + log_files = g_malloc0 (sizeof (FILE *) * 2); + log_files[0] = stdout; + } + +#ifndef GST_DISABLE_GST_DEBUG + if (!newline_regex) + newline_regex = + g_regex_new ("\n", G_REGEX_OPTIMIZE | G_REGEX_MULTILINE, 0, NULL); +#endif +} + +void +gst_validate_report_deinit (void) +{ + if (server_ostream) { + g_output_stream_close (server_ostream, NULL, NULL); + server_ostream = NULL; + } + + g_clear_object (&socket_client); + g_clear_object (&server_connection); +} + +GstValidateIssue * +gst_validate_issue_from_id (GstValidateIssueId issue_id) +{ + return g_hash_table_lookup (_gst_validate_issues, (gpointer) issue_id); +} + +/* TODO how are these functions going to work with extensions */ +const gchar * +gst_validate_report_level_get_name (GstValidateReportLevel level) +{ + switch (level) { + case GST_VALIDATE_REPORT_LEVEL_CRITICAL: + return "critical"; + case GST_VALIDATE_REPORT_LEVEL_WARNING: + return "warning"; + case GST_VALIDATE_REPORT_LEVEL_ISSUE: + return "issue"; + case GST_VALIDATE_REPORT_LEVEL_IGNORE: + return "ignore"; + case GST_VALIDATE_REPORT_LEVEL_EXPECTED: + return "expected"; + default: + return "unknown"; + } + + return NULL; +} + +GstValidateReportLevel +gst_validate_report_level_from_name (const gchar * level_name) +{ + if (g_strcmp0 (level_name, "critical") == 0) + return GST_VALIDATE_REPORT_LEVEL_CRITICAL; + + else if (g_strcmp0 (level_name, "warning") == 0) + return GST_VALIDATE_REPORT_LEVEL_WARNING; + + else if (g_strcmp0 (level_name, "issue") == 0) + return GST_VALIDATE_REPORT_LEVEL_ISSUE; + + else if (g_strcmp0 (level_name, "ignore") == 0) + return GST_VALIDATE_REPORT_LEVEL_IGNORE; + + return GST_VALIDATE_REPORT_LEVEL_UNKNOWN; +} + +gboolean +gst_validate_report_should_print (GstValidateReport * report) +{ + if ((!(_gst_validate_flags & GST_VALIDATE_PRINT_ISSUES) && + !(_gst_validate_flags & GST_VALIDATE_PRINT_WARNINGS) && + !(_gst_validate_flags & GST_VALIDATE_PRINT_CRITICALS))) { + return TRUE; + } + + if ((report->level <= GST_VALIDATE_REPORT_LEVEL_ISSUE && + _gst_validate_flags & GST_VALIDATE_PRINT_ISSUES) || + (report->level <= GST_VALIDATE_REPORT_LEVEL_WARNING && + _gst_validate_flags & GST_VALIDATE_PRINT_WARNINGS) || + (report->level <= GST_VALIDATE_REPORT_LEVEL_CRITICAL && + _gst_validate_flags & GST_VALIDATE_PRINT_CRITICALS)) { + + return TRUE; + } + + return FALSE; +} + +gboolean +gst_validate_report_check_abort (GstValidateReport * report) +{ + if ((report->level <= GST_VALIDATE_REPORT_LEVEL_ISSUE && + _gst_validate_flags & GST_VALIDATE_FATAL_ISSUES) || + (report->level <= GST_VALIDATE_REPORT_LEVEL_WARNING && + _gst_validate_flags & GST_VALIDATE_FATAL_WARNINGS) || + (report->level <= GST_VALIDATE_REPORT_LEVEL_CRITICAL && + _gst_validate_flags & GST_VALIDATE_FATAL_CRITICALS)) { + + return TRUE; + } + + return FALSE; +} + +GstValidateIssueId +gst_validate_report_get_issue_id (GstValidateReport * report) +{ + return gst_validate_issue_get_id (report->issue); +} + +static void +_report_free (GstValidateReport * report) +{ + g_free (report->message); + g_free (report->reporter_name); + g_free (report->trace); + g_free (report->dotfile_name); + g_list_free_full (report->shadow_reports, + (GDestroyNotify) gst_validate_report_unref); + g_list_free_full (report->repeated_reports, + (GDestroyNotify) gst_validate_report_unref); + g_mutex_clear (&report->shadow_reports_lock); + g_slice_free (GstValidateReport, report); +} + +static gboolean +gst_validate_report_should_generate_backtrace (GstValidateIssue * issue, + GstValidateReport * report, + GstValidateReportingDetails default_details, + GstValidateReportingDetails issue_type_details, + GstValidateReportingDetails reporter_details) +{ + if (issue->flags & GST_VALIDATE_ISSUE_FLAGS_FORCE_BACKTRACE) + return TRUE; + + if (issue->flags & GST_VALIDATE_ISSUE_FLAGS_NO_BACKTRACE) + return FALSE; + + if (default_details == GST_VALIDATE_SHOW_ALL) + return TRUE; + + if (issue_type_details == GST_VALIDATE_SHOW_ALL) + return TRUE; + + if (gst_validate_report_check_abort (report)) + return TRUE; + + if (report->level == GST_VALIDATE_REPORT_LEVEL_CRITICAL) + return TRUE; + + return FALSE; +} + +GstValidateReport * +gst_validate_report_new (GstValidateIssue * issue, + GstValidateReporter * reporter, const gchar * message) +{ + GstValidateReport *report = g_slice_new0 (GstValidateReport); + GstValidateReportingDetails reporter_details, default_details, + issue_type_details; + GstValidateRunner *runner = gst_validate_reporter_get_runner (reporter); + + gst_mini_object_init (((GstMiniObject *) report), 0, + _gst_validate_report_type, NULL, NULL, + (GstMiniObjectFreeFunction) _report_free); + GST_MINI_OBJECT_FLAG_SET (report, GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED); + + report->issue = issue; + /* The reporter is owning a ref on the report so it doesn't keep a ref to + * avoid reference cycles. But the report can also be used by + * GstValidateRunner *after* that the reporter has been destroyed, so we + * cache the reporter name to avoid crashing in + * gst_validate_report_print_detected_on if the reporter has been destroyed. + */ + report->reporter = reporter; + report->reporter_name = g_strdup (gst_validate_reporter_get_name (reporter)); + report->message = g_strdup (message); + g_mutex_init (&report->shadow_reports_lock); + report->timestamp = + gst_util_get_timestamp () - _gst_validate_report_start_time; + report->level = issue->default_level; + report->reporting_level = GST_VALIDATE_SHOW_UNKNOWN; + + reporter_details = gst_validate_reporter_get_reporting_level (reporter); + issue_type_details = gst_validate_runner_get_reporting_level_for_name (runner, + g_quark_to_string (issue->issue_id)); + default_details = gst_validate_runner_get_default_reporting_details (runner); + gst_object_unref (runner); + if (reporter_details != GST_VALIDATE_SHOW_ALL && + reporter_details != GST_VALIDATE_SHOW_UNKNOWN) + return report; + + if (gst_validate_report_should_generate_backtrace (issue, report, + default_details, issue_type_details, reporter_details)) + report->trace = gst_debug_get_stack_trace (GST_STACK_TRACE_SHOW_FULL); + + return report; +} + +void +gst_validate_report_unref (GstValidateReport * report) +{ + gst_mini_object_unref (GST_MINI_OBJECT (report)); +} + +GstValidateReport * +gst_validate_report_ref (GstValidateReport * report) +{ + return (GstValidateReport *) gst_mini_object_ref (GST_MINI_OBJECT (report)); +} + +void +gst_validate_printf (gpointer source, const gchar * format, ...) +{ + va_list var_args; + + va_start (var_args, format); + gst_validate_printf_valist (source, format, var_args); + va_end (var_args); +} + +typedef struct +{ + GString *str; + gint indent; + gint printed; +} PrintActionFieldData; + +static gboolean +_append_value (GQuark field_id, const GValue * value, PrintActionFieldData * d) +{ + gchar *val_str = NULL; + const gchar *fieldname = g_quark_to_string (field_id); + + if (g_str_has_prefix (fieldname, "__") && g_str_has_suffix (fieldname, "__")) + return TRUE; + + if (g_strcmp0 (fieldname, "repeat") == 0) + return TRUE; + + d->printed++; + if (G_VALUE_TYPE (value) == GST_TYPE_CLOCK_TIME) + val_str = g_strdup_printf ("%" GST_TIME_FORMAT, + GST_TIME_ARGS (g_value_get_uint64 (value))); + else + val_str = gst_value_serialize (value); + + g_string_append_printf (d->str, "\n%*c - ", d->indent, ' '); + g_string_append (d->str, fieldname); + g_string_append_len (d->str, "=", 1); + g_string_append (d->str, val_str); + + g_free (val_str); + + return TRUE; +} + +/** + * gst_validate_print_action: + * @action: (allow-none): The source object to log + * @message: The message to print out in the GstValidate logging system + * + * Print @message to the GstValidate logging system + */ +void +gst_validate_print_action (GstValidateAction * action, const gchar * message) +{ + GString *string = NULL; + + if (message == NULL) { + gint indent = (gst_validate_action_get_level (action) * 2); + PrintActionFieldData d = { NULL, indent, 0 }; + d.str = string = g_string_new (NULL); + + g_string_append_printf (string, "`%s` at %s:%d", action->type, + GST_VALIDATE_ACTION_FILENAME (action), + GST_VALIDATE_ACTION_LINENO (action)); + + if (GST_VALIDATE_ACTION_N_REPEATS (action)) + g_string_append_printf (string, " [%s=%d/%d]", + GST_VALIDATE_ACTION_RANGE_NAME (action) ? + GST_VALIDATE_ACTION_RANGE_NAME (action) : "repeat", action->repeat, + GST_VALIDATE_ACTION_N_REPEATS (action)); + + g_string_append (string, " ( "); + gst_structure_foreach (action->structure, + (GstStructureForeachFunc) _append_value, &d); + if (d.printed) + g_string_append_printf (string, "\n%*c)\n", indent, ' '); + else + g_string_append (string, ")\n"); + message = string->str; + } + + gst_validate_printf (action, "%s", message); + + if (string) + g_string_free (string, TRUE); +} + +static void +print_action_parameter (GString * string, GstValidateActionType * type, + GstValidateActionParameter * param) +{ + gchar *desc; + g_string_append_printf (string, "\n\n* `%s`:(%s): ", param->name, + param->mandatory ? "mandatory" : "optional"); + + if (g_strcmp0 (param->description, "")) { + desc = g_strdup (param->description); + } else { + desc = g_strdup ("__No description__"); + } + + g_string_append (string, desc); + g_free (desc); + + if (param->possible_variables) { + desc = + g_regex_replace (newline_regex, + param->possible_variables, -1, 0, "\n\n * ", 0, NULL); + g_string_append_printf (string, "\n\n Possible variables:\n\n * %s", + desc); + } + + if (param->types) + g_string_append_printf (string, "\n\n Possible types: `%s`", param->types); + + if (!param->mandatory) + g_string_append_printf (string, "\n\n Default: %s", param->def); + +} + +static void +print_action_parameter_prototype (GString * string, + GstValidateActionParameter * param, gboolean is_first) +{ + if (!is_first) + g_string_append (string, ","); + g_string_append (string, "\n "); + + if (!param->mandatory) + g_string_append (string, "["); + + g_string_append (string, param->name); + if (param->types) + g_string_append_printf (string, "=(%s)", param->types); + + if (!param->mandatory) + g_string_append (string, "]"); +} + +static int +sort_parameters (const GstValidateActionParameter * param1, + const GstValidateActionParameter * param2) +{ + if (param1->mandatory && !param2->mandatory) + return -1; + + if (!param1->mandatory && param2->mandatory) + return 1; + + return g_strcmp0 (param1->name, param2->name); +} + +void +gst_validate_printf_valist (gpointer source, const gchar * format, va_list args) +{ + gint i; + gchar *tmp; + GString *string = g_string_new (NULL); + + if (source) { + if (*(GType *) source == GST_TYPE_VALIDATE_ACTION) { + GstValidateAction *action = (GstValidateAction *) source; + gint indent = gst_validate_action_get_level (action) * 2; + + if (_action_check_and_set_printed (action)) + goto out; + + if (!indent) + g_string_assign (string, "Executing "); + else + g_string_append_printf (string, "%*c↳ Executing ", indent - 2, ' '); + } else if (*(GType *) source == GST_TYPE_VALIDATE_ACTION_TYPE) { + gint i; + gint n_params; + gboolean has_parameters = FALSE; + gboolean is_first = TRUE; + + GstValidateActionParameter playback_time_param = { + .name = "playback-time", + .description = "The playback time at which the action will be executed", + .mandatory = FALSE, + .types = "double,string", + .possible_variables = + "`position`: The current position in the stream\n" + "`duration`: The duration of the stream", + .def = "0.0" + }; + + GstValidateActionParameter on_message_param = { + .name = "on-message", + .description = + "Specify on what message type the action will be executed.\n" + " If both 'playback-time' and 'on-message' is specified, the action will be executed\n" + " on whatever happens first.", + .mandatory = FALSE, + .types = "string", + .possible_variables = NULL, + .def = NULL + }; + + + GstValidateActionType *type = GST_VALIDATE_ACTION_TYPE (source); + + /* Ignore private action types */ + if (g_str_has_prefix (type->name, "priv_")) + return; + + g_string_append_printf (string, "\n## %s\n\n", type->name); + + g_string_append_printf (string, "\n``` validate-scenario\n%s,", + type->name); + + for (n_params = 0; type->parameters[n_params].name != NULL; n_params++); + qsort (type->parameters, n_params, sizeof (GstValidateActionParameter), + (GCompareFunc) sort_parameters); + for (i = 0; type->parameters[i].name; i++) { + print_action_parameter_prototype (string, &type->parameters[i], + is_first); + is_first = FALSE; + } + + if (!IS_CONFIG_ACTION_TYPE (type->flags)) { + print_action_parameter_prototype (string, &playback_time_param, + is_first); + is_first = FALSE; + } + + g_string_append (string, ";\n```\n"); + + g_string_append_printf (string, "\n%s", type->description); + g_string_append_printf (string, + "\n * Implementer namespace: %s", type->implementer_namespace); + + if (IS_CONFIG_ACTION_TYPE (type->flags)) + g_string_append_printf (string, + "\n * Is config action (meaning it will be executing right " + "at the beginning of the execution of the pipeline)"); + + + if (type->parameters || !IS_CONFIG_ACTION_TYPE (type->flags)) + g_string_append_printf (string, "\n\n### Parameters"); + + if (type->parameters) { + has_parameters = TRUE; + for (i = 0; type->parameters[i].name; i++) { + print_action_parameter (string, type, &type->parameters[i]); + } + } + + if (!IS_CONFIG_ACTION_TYPE (type->flags)) { + print_action_parameter (string, type, &playback_time_param); + print_action_parameter (string, type, &on_message_param); + } + + + if ((type->flags & GST_VALIDATE_ACTION_TYPE_CAN_BE_OPTIONAL)) { + has_parameters = TRUE; + g_string_append_printf (string, + "\n optional : " + "Don't raise an error if this action hasn't been executed or failed" + "\n%-32s ### Possible types:" + "\n%-32s boolean" "\n%-32s Default: false", "", "", ""); + } + + if (!has_parameters) + g_string_append_printf (string, "\n\n ### No Parameters"); + } else if (GST_IS_VALIDATE_REPORTER (source) && + gst_validate_reporter_get_name (source)) { + g_string_printf (string, "\n%s --> ", + gst_validate_reporter_get_name (source)); + } else if (GST_IS_OBJECT (source)) { + g_string_printf (string, "\n%s --> ", GST_OBJECT_NAME (source)); + } else if (G_IS_OBJECT (source)) { + g_string_printf (string, "\n<%s@%p> --> ", G_OBJECT_TYPE_NAME (source), + source); + } + } + + tmp = gst_info_strdup_vprintf (format, args); + g_string_append (string, tmp); + g_free (tmp); + + if (!newline_regex) + newline_regex = + g_regex_new ("\n", G_REGEX_OPTIMIZE | G_REGEX_MULTILINE, 0, NULL); + +#ifndef GST_DISABLE_GST_DEBUG + { + gchar *str; + + str = g_regex_replace (newline_regex, string->str, string->len, 0, + "", 0, NULL); + + if (source) + GST_INFO ("%s", str); + else + GST_DEBUG ("%s", str); + + g_free (str); + } +#endif + + for (i = 0; log_files[i]; i++) { + fprintf (log_files[i], "%s", string->str); + fflush (log_files[i]); + } + +out: + g_string_free (string, TRUE); +} + +gboolean +gst_validate_report_set_master_report (GstValidateReport * report, + GstValidateReport * master_report) +{ + GList *tmp; + gboolean add_shadow_report = TRUE; + + if (master_report->reporting_level >= GST_VALIDATE_SHOW_MONITOR && + master_report->reporting_level != GST_VALIDATE_SHOW_SMART) { + return FALSE; + } + + report->master_report = master_report; + + GST_VALIDATE_REPORT_SHADOW_REPORTS_LOCK (master_report); + for (tmp = master_report->shadow_reports; tmp; tmp = tmp->next) { + GstValidateReport *shadow_report = (GstValidateReport *) tmp->data; + if (report->reporter == shadow_report->reporter) { + add_shadow_report = FALSE; + break; + } + } + if (add_shadow_report) + master_report->shadow_reports = + g_list_append (master_report->shadow_reports, + gst_validate_report_ref (report)); + GST_VALIDATE_REPORT_SHADOW_REPORTS_UNLOCK (master_report); + + return TRUE; +} + +void +gst_validate_report_print_level (GstValidateReport * report) +{ + gst_validate_printf (NULL, "%10s : %s\n", + gst_validate_report_level_get_name (report->level), + report->issue->summary); +} + +void +gst_validate_report_print_detected_on (GstValidateReport * report) +{ + GList *tmp; + + gst_validate_printf (NULL, "%*s Detected on <%s", + 12, "", report->reporter_name); + for (tmp = report->shadow_reports; tmp; tmp = tmp->next) { + GstValidateReport *shadow_report = (GstValidateReport *) tmp->data; + gst_validate_printf (NULL, ", %s", shadow_report->reporter_name); + } + gst_validate_printf (NULL, ">\n"); +} + +void +gst_validate_report_print_details (GstValidateReport * report) +{ + if (report->message) { + gint i; + gchar **lines = g_strsplit (report->message, "\n", -1); + + gst_validate_printf (NULL, "%*s Details : %s\n", 12, "", lines[0]); + for (i = 1; lines[i]; i++) + gst_validate_printf (NULL, "%*s%s\n", 21, "", lines[i]); + g_strfreev (lines); + } +} + +static void +gst_validate_report_print_trace (GstValidateReport * report) +{ + if (report->trace) { + gint i; + gchar **lines = g_strsplit (report->trace, "\n", -1); + + gst_validate_printf (NULL, "%*s backtrace :\n", 12, ""); + for (i = 0; lines[i]; i++) + gst_validate_printf (NULL, "%*s%s\n", 15, "", lines[i]); + g_strfreev (lines); + } +} + +static void +gst_validate_report_print_dotfile (GstValidateReport * report) +{ + const gchar *dotdir = g_getenv ("GST_DEBUG_DUMP_DOT_DIR"); + const gchar *doturl = g_getenv ("GST_VALIDATE_DEBUG_DUMP_DOT_URL"); + + if (!report->dotfile_name) + return; + + if (doturl) + gst_validate_printf (NULL, "%*s dotfile : %s%s%s.dot\n", 12, "", + doturl, G_DIR_SEPARATOR_S, report->dotfile_name); + else if (dotdir) + gst_validate_printf (NULL, "%*s dotfile : %s%s%s.dot\n", 12, "", + dotdir, G_DIR_SEPARATOR_S, report->dotfile_name); + else + gst_validate_printf (NULL, + "%*s dotfile : no dotfile produced as GST_DEBUG_DUMP_DOT_DIR is not set.\n", + 12, ""); +} + +void +gst_validate_report_print_description (GstValidateReport * report) +{ + if (report->issue->description) + gst_validate_printf (NULL, "%*s Description : %s\n", 12, "", + report->issue->description); +} + +void +gst_validate_report_printf (GstValidateReport * report) +{ + GList *tmp; + + gst_validate_report_print_level (report); + gst_validate_report_print_detected_on (report); + gst_validate_report_print_details (report); + for (tmp = report->repeated_reports; tmp; tmp = tmp->next) { + gst_validate_report_print_details (tmp->data); + } + gst_validate_report_print_dotfile (report); + gst_validate_report_print_trace (report); + + gst_validate_report_print_description (report); + gst_validate_printf (NULL, "\n"); +} + +void +gst_validate_report_set_reporting_level (GstValidateReport * report, + GstValidateReportingDetails level) +{ + report->reporting_level = level; +} + +void +gst_validate_report_add_repeated_report (GstValidateReport * report, + GstValidateReport * repeated_report) +{ + report->repeated_reports = + g_list_append (report->repeated_reports, + gst_validate_report_ref (repeated_report)); +} + + +void +gst_validate_print_position (GstClockTime position, GstClockTime duration, + gdouble rate, gchar * extra_info) +{ + JsonBuilder *jbuilder; + + gst_validate_printf (NULL, + "%c", GST_TIME_ARGS (position), GST_TIME_ARGS (duration), + rate, extra_info ? extra_info : "", output_is_tty ? '\r' : '\n'); + + if (!server_ostream) + return; + + jbuilder = json_builder_new (); + json_builder_begin_object (jbuilder); + json_builder_set_member_name (jbuilder, "type"); + json_builder_add_string_value (jbuilder, "position"); + json_builder_set_member_name (jbuilder, "position"); + json_builder_add_int_value (jbuilder, position); + json_builder_set_member_name (jbuilder, "duration"); + json_builder_add_int_value (jbuilder, duration); + json_builder_set_member_name (jbuilder, "speed"); + json_builder_add_double_value (jbuilder, rate); + json_builder_end_object (jbuilder); + + gst_validate_send (json_builder_get_root (jbuilder)); + g_object_unref (jbuilder); + + g_free (extra_info); +} + +void +gst_validate_skip_test (const gchar * format, ...) +{ + JsonBuilder *jbuilder; + va_list va_args; + gchar *tmp; + + va_start (va_args, format); + tmp = gst_info_strdup_vprintf (format, va_args); + va_end (va_args); + + if (!server_ostream) { + gchar *f = g_strconcat ("ok 1 # SKIP ", tmp, NULL); + + g_free (tmp); + gst_validate_printf (NULL, "%s", f); + return; + } + + jbuilder = json_builder_new (); + json_builder_begin_object (jbuilder); + json_builder_set_member_name (jbuilder, "type"); + json_builder_add_string_value (jbuilder, "skip-test"); + json_builder_set_member_name (jbuilder, "details"); + json_builder_add_string_value (jbuilder, tmp); + json_builder_end_object (jbuilder); + g_free (tmp); + + gst_validate_send (json_builder_get_root (jbuilder)); + g_object_unref (jbuilder); +} + +static void +print_issue (gpointer key, GstValidateIssue * issue, gpointer user_data) +{ + gst_validate_printf (NULL, "\n# `%s` (%" G_GUINTPTR_FORMAT ")\n\n", + g_quark_to_string (issue->issue_id), issue->issue_id); + gst_validate_printf (NULL, "%c%s\n\n", g_ascii_toupper (issue->summary[0]), + &issue->summary[1]); + if (issue->description) + gst_validate_printf (NULL, "%c%s\n\n", + g_ascii_toupper (issue->description[0]), &issue->description[1]); + gst_validate_printf (NULL, "Area: %s\n", issue->area); + gst_validate_printf (NULL, "Name: %s\n", issue->name); + gst_validate_printf (NULL, "Default severity: %s\n\n", + gst_validate_report_level_get_name (issue->default_level)); +} + +void +gst_validate_print_issues (void) +{ + g_return_if_fail (_gst_validate_issues); + + g_hash_table_foreach (_gst_validate_issues, (GHFunc) print_issue, NULL); +} + +void +gst_validate_error_structure (gpointer structure, const gchar * format, ...) +{ + gchar *filename = NULL; + gint lineno = -1; + gchar *tmp, *debug = NULL; + GString *f = g_string_new (NULL); + va_list var_args; + gchar *color = NULL; + + const gchar *endcolor = ""; + + if (g_log_writer_supports_color (fileno (stderr))) { + color = gst_debug_construct_term_color (GST_DEBUG_FG_RED); + endcolor = "\033[0m"; + } + + if (structure) { + if (GST_IS_STRUCTURE (structure)) { + filename = + g_strdup (gst_structure_get_string (structure, "__filename__")); + debug = g_strdup (gst_structure_get_string (structure, "__debug__")); + gst_structure_get_int (structure, "__lineno__", &lineno); + /* We are going to assert... we can boutcher the struct! */ + gst_structure_remove_fields (structure, "__filename__", "__lineno__", + "__debug__", NULL); + } else { + filename = g_strdup (GST_VALIDATE_ACTION_FILENAME (structure)); + debug = g_strdup (GST_VALIDATE_ACTION_DEBUG (structure)); + lineno = GST_VALIDATE_ACTION_LINENO (structure); + } + } + + va_start (var_args, format); + tmp = gst_info_strdup_vprintf (format, var_args); + va_end (var_args); + + g_string_append_printf (f, "%s:%d: %s\n", + filename ? filename : "Unknown", lineno, tmp); + + if (debug) + g_string_append (f, debug); + + g_print ("Bail out! %sERROR%s: %s\n\n", color ? color : "", endcolor, f->str); + g_string_free (f, TRUE); + g_free (debug); + g_free (color); + g_free (filename); + g_free (tmp); + + exit (-18); +} + +void +gst_validate_abort (const gchar * format, ...) +{ + va_list var_args; + gchar *tmp; + + va_start (var_args, format); + tmp = gst_info_strdup_vprintf (format, var_args); + va_end (var_args); + + g_print ("Bail out! %s\n", tmp); + exit (-18); +} + +gboolean +is_tty () +{ + return output_is_tty; +} diff --git a/validate/gst/validate/gst-validate-report.h b/validate/gst/validate/gst-validate-report.h new file mode 100644 index 0000000000..c16a420e71 --- /dev/null +++ b/validate/gst/validate/gst-validate-report.h @@ -0,0 +1,336 @@ +/* GStreamer + * Copyright (C) 2013 Thiago Santos + * + * gst-validate-monitor-report.h - Validate Element report structures and functions + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_VALIDATE_REPORT_H__ +#define __GST_VALIDATE_REPORT_H__ + +#include + +typedef struct _GstValidateReport GstValidateReport; +typedef guintptr GstValidateIssueId; + +#include +#include +#include +#include "gst-validate-types.h" + +G_BEGIN_DECLS + +GST_VALIDATE_API +GType gst_validate_report_get_type (void); +#define GST_TYPE_VALIDATE_REPORT (gst_validate_report_get_type ()) + +/** + * GstValidateDebugFlags: + * GST_VALIDATE_FATAL_DEFAULT: + * GST_VALIDATE_FATAL_ISSUES: + * GST_VALIDATE_FATAL_WARNINGS: + * GST_VALIDATE_FATAL_CRITICALS: + * GST_VALIDATE_PRINT_ISSUES: + * GST_VALIDATE_PRINT_WARNINGS: + * GST_VALIDATE_PRINT_CRITICALS: + */ +typedef enum { + GST_VALIDATE_FATAL_DEFAULT = 0, + GST_VALIDATE_FATAL_ISSUES = 1 << 0, + GST_VALIDATE_FATAL_WARNINGS = 1 << 1, + GST_VALIDATE_FATAL_CRITICALS = 1 << 2, + GST_VALIDATE_PRINT_ISSUES = 1 << 3, + GST_VALIDATE_PRINT_WARNINGS = 1 << 4, + GST_VALIDATE_PRINT_CRITICALS = 1 << 5 +} GstValidateDebugFlags; + +/** + * GstValidateReportLevel: + */ +typedef enum { + GST_VALIDATE_REPORT_LEVEL_CRITICAL, + GST_VALIDATE_REPORT_LEVEL_WARNING, + GST_VALIDATE_REPORT_LEVEL_ISSUE, + GST_VALIDATE_REPORT_LEVEL_IGNORE, + GST_VALIDATE_REPORT_LEVEL_UNKNOWN, + GST_VALIDATE_REPORT_LEVEL_EXPECTED, + GST_VALIDATE_REPORT_LEVEL_NUM_ENTRIES, +} GstValidateReportLevel; + +#define _QUARK g_quark_from_static_string + +#define BUFFER_BEFORE_SEGMENT _QUARK("buffer::before-segment") +#define BUFFER_IS_OUT_OF_SEGMENT _QUARK("buffer::is-out-of-segment") +#define BUFFER_TIMESTAMP_OUT_OF_RECEIVED_RANGE _QUARK("buffer::timestamp-out-of-received-range") +#define WRONG_FLOW_RETURN _QUARK("buffer::wrong-flow-return") +#define BUFFER_AFTER_EOS _QUARK("buffer::after-eos") +#define WRONG_BUFFER _QUARK("buffer::not-expected-one") +#define FLOW_ERROR_WITHOUT_ERROR_MESSAGE _QUARK("buffer::flow-error-without-error-message") +#define BUFFER_MISSING_DISCONT _QUARK("buffer::missing-discont") + +#define PULL_RANGE_FROM_WRONG_THREAD _QUARK("threading::pull-range-from-wrong-thread") + +#define CAPS_IS_MISSING_FIELD _QUARK("caps::is-missing-field") +#define CAPS_FIELD_HAS_BAD_TYPE _QUARK("caps::field-has-bad-type") +#define CAPS_EXPECTED_FIELD_NOT_FOUND _QUARK("caps::expected-field-not-found") +#define GET_CAPS_NOT_PROXYING_FIELDS _QUARK("caps::not-proxying-fields") +#define CAPS_FIELD_UNEXPECTED_VALUE _QUARK("caps::field-unexpected-value") + +#define EVENT_NEWSEGMENT_NOT_PUSHED _QUARK("event::newsegment-not-pushed") +#define SERIALIZED_EVENT_WASNT_PUSHED_IN_TIME _QUARK("event::serialized-event-wasnt-pushed-in-time") + +#define EOS_HAS_WRONG_SEQNUM _QUARK("event::eos-has-wrong-seqnum") +#define FLUSH_START_HAS_WRONG_SEQNUM _QUARK("event::flush-start-has-wrong-seqnum") +#define FLUSH_STOP_HAS_WRONG_SEQNUM _QUARK("event::flush-stop-has-wrong-seqnum") +#define SEGMENT_HAS_WRONG_SEQNUM _QUARK("event::segment-has-wrong-seqnum") +#define SEGMENT_HAS_WRONG_START _QUARK("event::segment-has-wrong-start") + + +#define EVENT_SERIALIZED_OUT_OF_ORDER _QUARK("event::serialized-out-of-order") +#define EVENT_NEW_SEGMENT_MISMATCH _QUARK("event::segment-mismatch") +#define EVENT_FLUSH_START_UNEXPECTED _QUARK("event::flush-start-unexpected") +#define EVENT_FLUSH_STOP_UNEXPECTED _QUARK("event::flush-stop-unexpected") +#define EVENT_CAPS_DUPLICATE _QUARK("event::caps-duplicate") +#define EVENT_SEEK_NOT_HANDLED _QUARK("event::seek-not-handled") +#define EVENT_SEEK_RESULT_POSITION_WRONG _QUARK("event::seek-result-position-wrong") +#define EVENT_SEEK_INVALID_SEQNUM _QUARK("event::seek-invalid_seqnum") +#define EVENT_EOS_WITHOUT_SEGMENT _QUARK("event::eos-without-segment") +#define EVENT_INVALID_SEQNUM _QUARK("event::invalid-seqnum") + +#define STATE_CHANGE_FAILURE _QUARK("state::change-failure") + +#define FILE_NO_STREAM_INFO _QUARK("file-checking::no-stream-info") +#define FILE_NO_STREAM_ID _QUARK("file-checking::no-stream-id") +#define FILE_TAG_DETECTION_INCORRECT _QUARK("file-checking::tag-detection-incorrect") +#define FILE_SIZE_INCORRECT _QUARK("file-checking::size-incorrect") +#define FILE_DURATION_INCORRECT _QUARK("file-checking::duration-incorrect") +#define FILE_SEEKABLE_INCORRECT _QUARK("file-checking::seekable-incorrect") +#define FILE_PROFILE_INCORRECT _QUARK("file-checking::profile-incorrect") +#define FILE_FRAMES_INCORRECT _QUARK("file-checking::frames-incorrect") +#define FILE_SEGMENT_INCORRECT _QUARK("file-checking::segment-incorrect") + +#define ALLOCATION_FAILURE _QUARK("runtime::allocation-failure") +#define MISSING_PLUGIN _QUARK("runtime::missing-plugin") +#define NOT_NEGOTIATED _QUARK("runtime::not-negotiated") +#define WARNING_ON_BUS _QUARK("runtime::warning-on-bus") +#define ERROR_ON_BUS _QUARK("runtime::error-on-bus") + +#define QUERY_POSITION_SUPERIOR_DURATION _QUARK("query::position-superior-duration") +#define QUERY_POSITION_OUT_OF_SEGMENT _QUARK("query::position-out-of-segment") + +#define SCENARIO_NOT_ENDED _QUARK("scenario::not-ended") +#define SCENARIO_FILE_MALFORMED _QUARK("scenario::malformed") +#define SCENARIO_ACTION_EXECUTION_ERROR _QUARK("scenario::execution-error") +#define SCENARIO_ACTION_CHECK_ERROR _QUARK("scenario::check-error") +#define SCENARIO_ACTION_TIMEOUT _QUARK("scenario::action-timeout") +#define SCENARIO_ACTION_EXECUTION_ISSUE _QUARK("scenario::execution-issue") + +#define CONFIG_LATENCY_TOO_HIGH _QUARK("config::latency-too-high") +#define CONFIG_TOO_MANY_BUFFERS_DROPPED _QUARK("config::too-many-buffers-dropped") +#define CONFIG_BUFFER_FREQUENCY_TOO_LOW _QUARK("config::buffer-frequency-too-low") + +#define G_LOG_ISSUE _QUARK("g-log::issue") +#define G_LOG_WARNING _QUARK("g-log::warning") +#define G_LOG_CRITICAL _QUARK("g-log::critical") + +/** + * GstValidateIssueFlags: + * GST_VALIDATE_ISSUE_FLAGS_NONE: No special flags for the issue type + * GST_VALIDATE_ISSUE_FLAGS_FULL_DETAILS: Always show all accurences of the issue in full details + * GST_VALIDATE_ISSUE_FLAGS_NO_BACKTRACE: Do not generate backtrace for the issue type + */ +typedef enum { + GST_VALIDATE_ISSUE_FLAGS_NONE = 0, + GST_VALIDATE_ISSUE_FLAGS_FULL_DETAILS = 1 << 0, + GST_VALIDATE_ISSUE_FLAGS_NO_BACKTRACE = 1 << 1, + + /** + * GST_VALIDATE_ISSUE_FLAGS_FORCE_BACKTRACE: + * + * Always generate backtrace, even if not a critical issue + * + * Since: 1.20 + */ + GST_VALIDATE_ISSUE_FLAGS_FORCE_BACKTRACE = 1 << 2, +} GstValidateIssueFlags; + +typedef struct { + GstValidateIssueId issue_id; + + /* Summary: one-liner translatable description of the issue */ + gchar *summary; + /* description: multi-line translatable description of: + * * what the issue is (and why it's an issue) + * * what the source problem could be + * * pointers to fixing the issue + */ + gchar *description; + + /* The name of the area of issue + * this one is in */ + gchar *area; + /* The name of the issue type */ + gchar *name; + + /* default_level: The default level of severity for this + * issue. */ + GstValidateReportLevel default_level; + + gint refcount; + + GstValidateIssueFlags flags; + + gpointer _gst_reserved[GST_PADDING]; + +} GstValidateIssue; + +GST_VALIDATE_API +GType gst_validate_issue_get_type (void); + +struct _GstValidateReport { + GstMiniObject mini_object; + + /* issue: The issue this report corresponds to (to get description, summary,...) */ + GstValidateIssue *issue; + + GstValidateReportLevel level; + + /* The reporter that reported the issue (to get names, info, ...) */ + GstValidateReporter *reporter; + + /* timestamp: The time at which this issue happened since + * the process start (to stay in sync with gst logging) */ + GstClockTime timestamp; + + /* message: issue-specific message. Gives more detail on the actual + * issue. Can be NULL */ + gchar *message; + + /* When reporter->intercept_report returns KEEP, the report is not + * added to the runner. It can be added as a "shadow_report" to + * the upstream report, which is tracked by the runner. */ + GMutex shadow_reports_lock; + GstValidateReport *master_report; + GList *shadow_reports; + + /* Lists the reports that were repeated inside the same reporter */ + GList *repeated_reports; + + GstValidateReportingDetails reporting_level; + gchar *reporter_name; + gchar *trace; + gchar *dotfile_name; + + gpointer _gst_reserved[GST_PADDING - 2]; +}; + +void gst_validate_report_add_message (GstValidateReport *report, + const gchar *message); + +#define GST_VALIDATE_ISSUE_FORMAT G_GUINTPTR_FORMAT " (%s) : %s: %s" +#define GST_VALIDATE_ISSUE_ARGS(i) gst_validate_issue_get_id (i), \ + gst_validate_report_level_get_name (i->default_level), \ + i->area, \ + i->summary + +#define GST_VALIDATE_ERROR_REPORT_PRINT_FORMAT GST_TIME_FORMAT " <%s>: %" GST_VALIDATE_ISSUE_FORMAT ": %s" +#define GST_VALIDATE_REPORT_PRINT_ARGS(r) GST_TIME_ARGS (r->timestamp), \ + gst_validate_reporter_get_name (r->reporter), \ + GST_VALIDATE_ISSUE_ARGS (r->issue), \ + r->message +GST_VALIDATE_API +void gst_validate_report_init (void); +GST_VALIDATE_API +GstValidateIssue *gst_validate_issue_from_id (GstValidateIssueId issue_id); +GST_VALIDATE_API +GstValidateIssueId gst_validate_issue_get_id (GstValidateIssue * issue); +GST_VALIDATE_API +void gst_validate_issue_register (GstValidateIssue * issue); +GST_VALIDATE_API +GstValidateIssue *gst_validate_issue_new (GstValidateIssueId issue_id, const gchar * summary, + const gchar * description, + GstValidateReportLevel default_level); +GST_VALIDATE_API +GstValidateIssue* gst_validate_issue_new_full(GstValidateIssueId issue_id, const gchar* summary, + const gchar* description, GstValidateReportLevel default_level, + GstValidateIssueFlags flags); +GST_VALIDATE_API +void gst_validate_issue_set_default_level (GstValidateIssue *issue, + GstValidateReportLevel default_level); + +GST_VALIDATE_API +GstValidateReport *gst_validate_report_new (GstValidateIssue * issue, + GstValidateReporter * reporter, + const gchar * message); +GST_VALIDATE_API +void gst_validate_report_unref (GstValidateReport * report); +GST_VALIDATE_API +GstValidateReport *gst_validate_report_ref (GstValidateReport * report); + +GST_VALIDATE_API +GstValidateIssueId gst_validate_report_get_issue_id (GstValidateReport * report); + +GST_VALIDATE_API +gboolean gst_validate_report_check_abort (GstValidateReport * report); +GST_VALIDATE_API +void gst_validate_report_printf (GstValidateReport * report); +GST_VALIDATE_API +void gst_validate_report_print_level (GstValidateReport *report); +GST_VALIDATE_API +void gst_validate_report_print_detected_on (GstValidateReport *report); +GST_VALIDATE_API +void gst_validate_report_print_details (GstValidateReport *report); +GST_VALIDATE_API +void gst_validate_report_print_description (GstValidateReport *report); + +GST_VALIDATE_API +const gchar * gst_validate_report_level_get_name (GstValidateReportLevel level); + +GST_VALIDATE_API +void gst_validate_printf (gpointer source, + const gchar * format, + ...) G_GNUC_PRINTF (2, 3) G_GNUC_NO_INSTRUMENT; +GST_VALIDATE_API +void gst_validate_print_action (GstValidateAction *action, const gchar * message); +GST_VALIDATE_API +void gst_validate_printf_valist (gpointer source, + const gchar * format, + va_list args) G_GNUC_PRINTF (2, 0) G_GNUC_NO_INSTRUMENT; +GST_VALIDATE_API +gboolean gst_validate_report_should_print (GstValidateReport * report); +GST_VALIDATE_API +gboolean gst_validate_report_set_master_report(GstValidateReport *report, GstValidateReport *master_report); +GST_VALIDATE_API +void gst_validate_report_set_reporting_level (GstValidateReport *report, GstValidateReportingDetails level); +GST_VALIDATE_API +void gst_validate_report_add_repeated_report (GstValidateReport *report, GstValidateReport *repeated_report); +GST_VALIDATE_API +GstValidateReportLevel gst_validate_report_level_from_name (const gchar *level_name); +GST_VALIDATE_API +void gst_validate_print_position(GstClockTime position, GstClockTime duration, gdouble rate, gchar* extra_info); +GST_VALIDATE_API void gst_validate_print_issues (void); + +GST_VALIDATE_API +void gst_validate_error_structure (gpointer action, const gchar* format, ...) G_GNUC_PRINTF (2, 3); +GST_VALIDATE_API +void gst_validate_abort (const gchar * format, ...) G_GNUC_PRINTF (1, 2); +GST_VALIDATE_API +void gst_validate_skip_test (const gchar* format, ...); +G_END_DECLS + +#endif /* __GST_VALIDATE_REPORT_H__ */ + diff --git a/validate/gst/validate/gst-validate-reporter.c b/validate/gst/validate/gst-validate-reporter.c new file mode 100644 index 0000000000..960cfd4c53 --- /dev/null +++ b/validate/gst/validate/gst-validate-reporter.c @@ -0,0 +1,566 @@ +/* GStreamer + * + * Copyright (C) 2013 Thibault Saunier + * + * gst-validate-reporter.c + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/** + * SECTION:gst-validate-reporter + * @title: GstValidateReporter + * @short_description: A #GInterface that allows #GObject to be used as originator of + * issues in the GstValidate reporting system + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include "gst-validate-internal.h" +#include "gst-validate-reporter.h" +#include "gst-validate-report.h" + +#define REPORTER_PRIVATE "gst-validate-reporter-private" + +typedef struct _GstValidateReporterPrivate +{ + GWeakRef runner; + GHashTable *reports; + char *name; + guint log_handler_id; + GMutex reports_lock; +} GstValidateReporterPrivate; + +static GstValidateReporterPrivate *g_log_handler = NULL; + +G_DEFINE_INTERFACE (GstValidateReporter, gst_validate_reporter, G_TYPE_OBJECT); + +static void +gst_validate_reporter_default_init (GstValidateReporterInterface * iface) +{ + g_object_interface_install_property (iface, + g_param_spec_object ("validate-runner", "Validate Runner", + "The Validate runner to report errors to", + GST_TYPE_VALIDATE_RUNNER, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); +} + +static void +_free_priv (GstValidateReporterPrivate * priv) +{ + if (g_log_handler == priv) { + g_log_set_default_handler (g_log_default_handler, NULL); + g_log_handler = NULL; + } + + g_hash_table_unref (priv->reports); + g_free (priv->name); + g_mutex_clear (&priv->reports_lock); + g_weak_ref_clear (&priv->runner); + g_slice_free (GstValidateReporterPrivate, priv); +} + +static GstValidateReporterPrivate * +gst_validate_reporter_get_priv (GstValidateReporter * reporter) +{ + GstValidateReporterPrivate *priv; + + priv = g_object_get_data (G_OBJECT (reporter), REPORTER_PRIVATE); + + if (priv == NULL) { + priv = g_slice_new0 (GstValidateReporterPrivate); + priv->reports = g_hash_table_new_full (g_direct_hash, + g_direct_equal, NULL, (GDestroyNotify) gst_validate_report_unref); + + g_mutex_init (&priv->reports_lock); + g_object_set_data_full (G_OBJECT (reporter), REPORTER_PRIVATE, priv, + (GDestroyNotify) _free_priv); + } + + return priv; +} + +#define GST_VALIDATE_REPORTER_REPORTS_LOCK(r) \ + G_STMT_START { \ + (g_mutex_lock (&gst_validate_reporter_get_priv(GST_VALIDATE_REPORTER_CAST(r))->reports_lock)); \ + } G_STMT_END + +#define GST_VALIDATE_REPORTER_REPORTS_UNLOCK(r) \ + G_STMT_START { \ + (g_mutex_unlock (&gst_validate_reporter_get_priv(GST_VALIDATE_REPORTER_CAST(r))->reports_lock)); \ + } G_STMT_END + +static GstValidateInterceptionReturn +gst_validate_reporter_intercept_report (GstValidateReporter * reporter, + GstValidateReport * report) +{ + GstValidateInterceptionReturn ret = GST_VALIDATE_REPORTER_REPORT; + GstValidateReporterInterface *iface = + GST_VALIDATE_REPORTER_GET_INTERFACE (reporter); + + if (iface->intercept_report) { + ret = iface->intercept_report (reporter, report); + } + + return ret; +} + +GstValidateReportingDetails +gst_validate_reporter_get_reporting_level (GstValidateReporter * reporter) +{ + GstValidateReportingDetails ret = GST_VALIDATE_SHOW_UNKNOWN; + GstValidateReporterInterface *iface = + GST_VALIDATE_REPORTER_GET_INTERFACE (reporter); + + if (iface->get_reporting_level) { + ret = iface->get_reporting_level (reporter); + } + + return ret; +} + +/** + * gst_validate_reporter_get_pipeline: + * @reporter: The reporter to get the pipeline from + * + * Returns: (transfer full) (allow-none): The #GstPipeline + */ +GstPipeline * +gst_validate_reporter_get_pipeline (GstValidateReporter * reporter) +{ + GstValidateReporterInterface *iface = + GST_VALIDATE_REPORTER_GET_INTERFACE (reporter); + + if (iface->get_pipeline) + return iface->get_pipeline (reporter); + + return NULL; +} + +GstValidateReport * +gst_validate_reporter_get_report (GstValidateReporter * reporter, + GstValidateIssueId issue_id) +{ + GstValidateReport *report; + GstValidateReporterPrivate *priv = gst_validate_reporter_get_priv (reporter); + + GST_VALIDATE_REPORTER_REPORTS_LOCK (reporter); + report = g_hash_table_lookup (priv->reports, (gconstpointer) issue_id); + GST_VALIDATE_REPORTER_REPORTS_UNLOCK (reporter); + + return report; +} + +void +gst_validate_report_valist (GstValidateReporter * reporter, + GstValidateIssueId issue_id, const gchar * format, va_list var_args) +{ + GstValidateReport *report, *prev_report; + gchar *message, *combo; + va_list vacopy; + GstValidateIssue *issue; + GstValidateReporterPrivate *priv = gst_validate_reporter_get_priv (reporter); + GstValidateInterceptionReturn int_ret; + GstValidateRunner *runner = NULL; + + issue = gst_validate_issue_from_id (issue_id); + + g_return_if_fail (issue != NULL); + g_return_if_fail (GST_IS_VALIDATE_REPORTER (reporter)); + + G_VA_COPY (vacopy, var_args); + message = gst_info_strdup_vprintf (format, vacopy); + report = gst_validate_report_new (issue, reporter, message); + +#ifndef GST_DISABLE_GST_DEBUG + combo = + g_strdup_printf ("<%s> %" GST_VALIDATE_ISSUE_FORMAT " : %s", priv->name, + GST_VALIDATE_ISSUE_ARGS (issue), format); + G_VA_COPY (vacopy, var_args); + if (report->level == GST_VALIDATE_REPORT_LEVEL_CRITICAL) { + gst_debug_log_valist (GST_CAT_DEFAULT, GST_LEVEL_ERROR, __FILE__, + GST_FUNCTION, __LINE__, NULL, combo, vacopy); + } else if (report->level == GST_VALIDATE_REPORT_LEVEL_WARNING) + gst_debug_log_valist (GST_CAT_DEFAULT, GST_LEVEL_WARNING, __FILE__, + GST_FUNCTION, __LINE__, NULL, combo, vacopy); + else if (report->level == GST_VALIDATE_REPORT_LEVEL_ISSUE) + gst_debug_log_valist (GST_CAT_DEFAULT, GST_LEVEL_LOG, __FILE__, + GST_FUNCTION, __LINE__, (GObject *) NULL, combo, vacopy); + else + gst_debug_log_valist (GST_CAT_DEFAULT, GST_LEVEL_DEBUG, __FILE__, + GST_FUNCTION, __LINE__, NULL, combo, vacopy); + g_free (combo); +#endif + va_end (vacopy); + + int_ret = gst_validate_reporter_intercept_report (reporter, report); + + if (int_ret == GST_VALIDATE_REPORTER_DROP) { + gst_validate_report_unref (report); + goto done; + } + + prev_report = g_hash_table_lookup (priv->reports, (gconstpointer) issue_id); + + runner = gst_validate_reporter_get_runner (reporter); + if (prev_report && prev_report->level != GST_VALIDATE_REPORT_LEVEL_EXPECTED) { + GstValidateReportingDetails reporter_level = + gst_validate_reporter_get_reporting_level (reporter); + GstValidateReportingDetails runner_level = GST_VALIDATE_SHOW_UNKNOWN; + + if (runner) + runner_level = gst_validate_runner_get_default_reporting_level (runner); + + if ((reporter_level == GST_VALIDATE_SHOW_ALL || + (runner_level == GST_VALIDATE_SHOW_ALL && + reporter_level == GST_VALIDATE_SHOW_UNKNOWN)) || + (issue->flags & GST_VALIDATE_ISSUE_FLAGS_FULL_DETAILS)) { + + gst_validate_report_add_repeated_report (prev_report, report); + } + + gst_validate_report_unref (report); + goto done; + } + + GST_VALIDATE_REPORTER_REPORTS_LOCK (reporter); + g_hash_table_insert (priv->reports, (gpointer) issue_id, report); + GST_VALIDATE_REPORTER_REPORTS_UNLOCK (reporter); + + if (runner && int_ret == GST_VALIDATE_REPORTER_REPORT) { + gst_validate_runner_add_report (runner, report); + } + + if (gst_validate_report_check_abort (report)) { + if (runner) + gst_validate_runner_printf (runner); + + gst_validate_abort ("Fatal report received: %" + GST_VALIDATE_ERROR_REPORT_PRINT_FORMAT, + GST_VALIDATE_REPORT_PRINT_ARGS (report)); + } + +done: + if (runner) + gst_object_unref (runner); + + g_free (message); +} + +static void +gst_validate_default_log_hanlder (const gchar * log_domain, + GLogLevelFlags log_level, const gchar * message, gpointer user_data) +{ + gchar *trace = gst_debug_get_stack_trace (GST_STACK_TRACE_SHOW_FULL); + + if (trace) { + gst_validate_printf (NULL, "\nStack trace:\n%s\n", trace); + g_free (trace); + } + + g_log_default_handler (log_domain, log_level, message, user_data); +} + +static void +gst_validate_reporter_destroyed (gpointer udata, GObject * freed_reporter) +{ + g_log_set_handler ("GStreamer", + G_LOG_LEVEL_MASK, (GLogFunc) gst_validate_default_log_hanlder, NULL); + g_log_set_handler ("GLib", + G_LOG_LEVEL_MASK, (GLogFunc) gst_validate_default_log_hanlder, NULL); + g_log_set_handler ("GLib-GObject", + G_LOG_LEVEL_MASK, (GLogFunc) gst_validate_default_log_hanlder, NULL); +} + +static void +gst_validate_reporter_g_log_func (const gchar * log_domain, + GLogLevelFlags log_level, const gchar * message, + GstValidateReporter * reporter) +{ + if (log_level & G_LOG_LEVEL_ERROR) + gst_validate_default_log_hanlder (log_domain, log_level, message, reporter); + else if (log_level & G_LOG_LEVEL_CRITICAL) + GST_VALIDATE_REPORT (reporter, G_LOG_CRITICAL, "%s", message); + else if (log_level & G_LOG_LEVEL_WARNING) + GST_VALIDATE_REPORT (reporter, G_LOG_WARNING, "%s", message); +} + +/** + * gst_validate_report: + * @reporter: The source of the new report + * @issue_id: The #GstValidateIssueId of the issue + * @format: The format of the message describing the issue in a printf + * format followed by the parameters. + * @...: Substitution arguments for @format + * + * Reports a new issue in the GstValidate reporting system. + * + * You can also use #GST_VALIDATE_REPORT instead. + */ +void +gst_validate_report (GstValidateReporter * reporter, + GstValidateIssueId issue_id, const gchar * format, ...) +{ + va_list var_args; + + va_start (var_args, format); + gst_validate_report_valist (reporter, issue_id, format, var_args); + va_end (var_args); +} + +/** + * gst_validate_report_action: + * @reporter: The source of the new report + * @action: The action reporting the issue + * @issue_id: The #GstValidateIssueId of the issue + * @format: The format of the message describing the issue in a printf + * format followed by the parameters. + * @...: Substitution arguments for @format + * + * Reports a new issue in the GstValidate reporting system specifying @action + * as failling action . + * + * You can also use #GST_VALIDATE_REPORT instead. + */ +void +gst_validate_report_action (GstValidateReporter * reporter, + GstValidateAction * action, GstValidateIssueId issue_id, + const gchar * format, ...) +{ + va_list var_args, var_copy; + GString *f; + + if (!action) { + f = g_string_new (format); + goto done; + } + + f = g_string_new (NULL); + g_string_append_printf (f, "\n> %s:%d", GST_VALIDATE_ACTION_FILENAME (action), + GST_VALIDATE_ACTION_LINENO (action)); + + if (GST_VALIDATE_ACTION_N_REPEATS (action)) + g_string_append_printf (f, " (repeat: %d/%d)", + action->repeat, GST_VALIDATE_ACTION_N_REPEATS (action)); + + g_string_append_printf (f, "\n%s", GST_VALIDATE_ACTION_DEBUG (action)); + if (gst_validate_action_get_level (action)) { + gchar *subaction_str = gst_structure_to_string (action->structure); + + g_string_append_printf (f, "\n |-> %s", subaction_str); + g_free (subaction_str); + } + + g_string_append_printf (f, "\n >\n > %s", format); + +done: + va_start (var_args, format); + G_VA_COPY (var_copy, var_args); + gst_validate_report_valist (reporter, issue_id, f->str, var_args); + if (action) { + gint i, indent = gst_validate_action_get_level (action) * 2; + gchar *message, **lines, *color = NULL; + const gchar *endcolor = ""; + + if (g_log_writer_supports_color (fileno (stderr))) { + color = gst_debug_construct_term_color (GST_DEBUG_FG_RED); + endcolor = "\033[0m"; + } + gst_validate_printf (NULL, "%*s%s> Error%s:\n", indent, "", + color ? color : "", endcolor); + + message = gst_info_strdup_vprintf (f->str, var_copy); + lines = g_strsplit (message, "\n", -1); + for (i = 1; lines[i]; i++) + gst_validate_printf (NULL, "%*s%s>%s %s\n", indent, "", color, endcolor, + lines[i]); + g_strfreev (lines); + g_free (message); + g_free (color); + } + va_end (var_args); + va_end (var_copy); + + g_string_free (f, TRUE); +} + +void +gst_validate_reporter_report_simple (GstValidateReporter * reporter, + GstValidateIssueId issue_id, const gchar * message) +{ + gst_validate_report (reporter, issue_id, "%s", message); +} + +/** + * gst_validate_reporter_set_name: + * @reporter: The reporter to set the name on + * @name: (transfer full): The name of the reporter + * + * Sets @ name on @reporter + */ +void +gst_validate_reporter_set_name (GstValidateReporter * reporter, gchar * name) +{ + GstValidateReporterPrivate *priv = gst_validate_reporter_get_priv (reporter); + + g_free (priv->name); + + priv->name = name; +} + +const gchar * +gst_validate_reporter_get_name (GstValidateReporter * reporter) +{ + GstValidateReporterPrivate *priv = gst_validate_reporter_get_priv (reporter); + + return priv->name; +} + +/** + * gst_validate_reporter_get_runner: + * @reporter: The reporter to get the runner from + * + * Returns: (transfer full): The runner + */ +GstValidateRunner * +gst_validate_reporter_get_runner (GstValidateReporter * reporter) +{ + GstValidateReporterPrivate *priv = gst_validate_reporter_get_priv (reporter); + + return g_weak_ref_get (&priv->runner); +} + +void +gst_validate_reporter_set_runner (GstValidateReporter * reporter, + GstValidateRunner * runner) +{ + GstValidateReporterPrivate *priv = gst_validate_reporter_get_priv (reporter); + + g_weak_ref_set (&priv->runner, runner); + + g_object_notify (G_OBJECT (reporter), "validate-runner"); +} + +/** + * gst_validate_reporter_set_handle_g_logs: + * @reporter: The #GstValidateReporter to set has the handler for g_log + * + * Set @reporter has the 'source' of any g_log happening during the + * execution. Usually the monitor of the first #GstPipeline is used + * to handle g_logs. + * + * Basically this function is used in order to start tracking any + * issue reported with g_log in the process into GstValidate report + * in the GstValidate reporting system. + */ +void +gst_validate_reporter_set_handle_g_logs (GstValidateReporter * reporter) +{ + g_log_set_default_handler ((GLogFunc) gst_validate_reporter_g_log_func, + reporter); + + g_log_set_handler ("GStreamer", + G_LOG_LEVEL_MASK, (GLogFunc) gst_validate_reporter_g_log_func, reporter); + + g_log_set_handler ("GLib", + G_LOG_LEVEL_MASK, (GLogFunc) gst_validate_reporter_g_log_func, reporter); + + + g_log_set_handler ("GLib-GObject", + G_LOG_LEVEL_MASK, (GLogFunc) gst_validate_reporter_g_log_func, reporter); + + g_log_handler = gst_validate_reporter_get_priv (reporter); + g_object_weak_ref (G_OBJECT (reporter), gst_validate_reporter_destroyed, + NULL); + +} + +/** + * gst_validate_reporter_get_reports: + * @reporter: a #GstValidateReporter + * + * Get the list of reports present in the reporter. + * + * Returns: (transfer full) (element-type GstValidateReport): the list of + * #GstValidateReport present in the reporter. + * The caller should unref each report once it is done with them. + */ +GList * +gst_validate_reporter_get_reports (GstValidateReporter * reporter) +{ + GstValidateReporterPrivate *priv; + GList *reports, *tmp; + GList *ret = NULL; + + priv = g_object_get_data (G_OBJECT (reporter), REPORTER_PRIVATE); + + GST_VALIDATE_REPORTER_REPORTS_LOCK (reporter); + reports = g_hash_table_get_values (priv->reports); + for (tmp = reports; tmp; tmp = tmp->next) { + ret = + g_list_append (ret, + gst_validate_report_ref ((GstValidateReport *) (tmp->data))); + } + g_list_free (reports); + GST_VALIDATE_REPORTER_REPORTS_UNLOCK (reporter); + + return ret; +} + +/** + * gst_validate_reporter_get_reports_count: + * @reporter: a #GstValidateReporter + * + * Get the number of reports present in the reporter. + * + * Returns: the number of reports currently present in @reporter. + */ +gint +gst_validate_reporter_get_reports_count (GstValidateReporter * reporter) +{ + GstValidateReporterPrivate *priv; + gint ret; + + priv = g_object_get_data (G_OBJECT (reporter), REPORTER_PRIVATE); + + GST_VALIDATE_REPORTER_REPORTS_LOCK (reporter); + ret = g_hash_table_size (priv->reports); + GST_VALIDATE_REPORTER_REPORTS_UNLOCK (reporter); + + return ret; +} + +/** + * gst_validate_reporter_purge_reports: + * @reporter: a #GstValidateReporter + * + * Remove all the #GstValidateReport from @reporter. This should be called + * before unreffing the reporter to break cyclic references. + */ +void +gst_validate_reporter_purge_reports (GstValidateReporter * reporter) +{ + GstValidateReporterPrivate *priv; + + priv = g_object_get_data (G_OBJECT (reporter), REPORTER_PRIVATE); + + GST_VALIDATE_REPORTER_REPORTS_LOCK (reporter); + g_hash_table_remove_all (priv->reports); + GST_VALIDATE_REPORTER_REPORTS_UNLOCK (reporter); +} diff --git a/validate/gst/validate/gst-validate-reporter.h b/validate/gst/validate/gst-validate-reporter.h new file mode 100644 index 0000000000..c4e6180368 --- /dev/null +++ b/validate/gst/validate/gst-validate-reporter.h @@ -0,0 +1,158 @@ +/* GStreamer + * + * Copyright (C) 2013 Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifndef _GST_VALIDATE_REPORTER_ +#define _GST_VALIDATE_REPORTER_ + +typedef struct _GstValidateReporter GstValidateReporter; +typedef struct _GstValidateReporterInterface GstValidateReporterInterface; + +#include +#include +#include +#include +#include +#include + +G_BEGIN_DECLS + +/* GstValidateReporter interface declarations */ +#ifndef __GI_SCANNER__ +#define GST_TYPE_VALIDATE_REPORTER (gst_validate_reporter_get_type ()) +#define GST_VALIDATE_REPORTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_VALIDATE_REPORTER, GstValidateReporter)) +#define GST_IS_VALIDATE_REPORTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_VALIDATE_REPORTER)) +#define GST_VALIDATE_REPORTER_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GST_TYPE_VALIDATE_REPORTER, GstValidateReporterInterface)) +#define GST_VALIDATE_REPORTER_CAST(obj) ((GstValidateReporter *) obj) +#endif + +/** + * GST_VALIDATE_REPORT: + * @m: The #GstValidateReporter where the issue happened + * @issue_id: The #GstValidateIssueId of the issue + * @...: The format of the message describing the issue in a printf + * format, followed by the parameters. + * + * Reports a new issue in the GstValidate reporting system with @m + * as the source of that issue. + */ +#ifdef G_HAVE_ISO_VARARGS +#define GST_VALIDATE_REPORT(m, issue_id, ...) \ + G_STMT_START { \ + gst_validate_report (GST_VALIDATE_REPORTER (m), \ + issue_id, \ + __VA_ARGS__ ); \ + } G_STMT_END + +#define GST_VALIDATE_REPORT_ACTION(m, a, issue_id, ...) \ + G_STMT_START { \ + gst_validate_report_action (GST_VALIDATE_REPORTER (m), a, \ + issue_id, \ + __VA_ARGS__ ); \ + } G_STMT_END + +#else /* G_HAVE_GNUC_VARARGS */ +#ifdef G_HAVE_GNUC_VARARGS +#define GST_VALIDATE_REPORT(m, issue_id, args...) \ + G_STMT_START { \ + gst_validate_report (GST_VALIDATE_REPORTER (m), \ + issue_id, ##args ); \ + } G_STMT_END +#define GST_VALIDATE_REPORT_ACTION(m, a, issue_id, args...) \ + G_STMT_START { \ + gst_validate_report_action (GST_VALIDATE_REPORTER (m), a, \ + issue_id, ##args ); \ + } G_STMT_END +#endif /* G_HAVE_ISO_VARARGS */ +#endif /* G_HAVE_GNUC_VARARGS */ +GST_VALIDATE_API +GType gst_validate_reporter_get_type (void); + +/** + * GstValidateInterceptionReturn: + * @GST_VALIDATE_REPORTER_DROP: The report will be completely ignored. + * @GST_VALIDATE_REPORTER_KEEP: The report will be kept by the reporter, + * but not reported to the runner. + * @GST_VALIDATE_REPORTER_REPORT: The report will be kept by the reporter + * and reported to the runner. + */ +typedef enum +{ + GST_VALIDATE_REPORTER_DROP, + GST_VALIDATE_REPORTER_KEEP, + GST_VALIDATE_REPORTER_REPORT +} GstValidateInterceptionReturn; + +/** + * GstValidateReporter: + */ +struct _GstValidateReporterInterface +{ + GTypeInterface parent; + + GstValidateInterceptionReturn (*intercept_report) (GstValidateReporter * reporter, + GstValidateReport * report); + GstValidateReportingDetails (*get_reporting_level) (GstValidateReporter * reporter); + GstPipeline * (*get_pipeline) (GstValidateReporter *reporter); +}; + +GST_VALIDATE_API +void gst_validate_reporter_set_name (GstValidateReporter * reporter, + gchar * name); +GST_VALIDATE_API +const gchar * gst_validate_reporter_get_name (GstValidateReporter * reporter); +GST_VALIDATE_API +GstValidateRunner * gst_validate_reporter_get_runner (GstValidateReporter *reporter); +GST_VALIDATE_API +void gst_validate_reporter_init (GstValidateReporter * reporter, const gchar *name); +GST_VALIDATE_API +void gst_validate_report (GstValidateReporter * reporter, GstValidateIssueId issue_id, + const gchar * format, ...) G_GNUC_PRINTF (3, 4) G_GNUC_NO_INSTRUMENT; +GST_VALIDATE_API +void gst_validate_report_action (GstValidateReporter * reporter, + GstValidateAction *action, + GstValidateIssueId issue_id, + const gchar * format, ...) G_GNUC_PRINTF (4, 5) G_GNUC_NO_INSTRUMENT; +GST_VALIDATE_API +void gst_validate_report_valist (GstValidateReporter * reporter, GstValidateIssueId issue_id, + const gchar * format, va_list var_args) G_GNUC_PRINTF (3, 0); +GST_VALIDATE_API void +gst_validate_reporter_report_simple (GstValidateReporter * reporter, GstValidateIssueId issue_id, + const gchar * message); + +GST_VALIDATE_API +void gst_validate_reporter_set_runner (GstValidateReporter * reporter, GstValidateRunner *runner); +GST_VALIDATE_API +void gst_validate_reporter_set_handle_g_logs (GstValidateReporter * reporter); +GST_VALIDATE_API +GstValidateReport * gst_validate_reporter_get_report (GstValidateReporter *reporter, + GstValidateIssueId issue_id); +GST_VALIDATE_API +GList * gst_validate_reporter_get_reports (GstValidateReporter * reporter); +GST_VALIDATE_API +gint gst_validate_reporter_get_reports_count (GstValidateReporter *reporter); +GST_VALIDATE_API +GstValidateReportingDetails gst_validate_reporter_get_reporting_level (GstValidateReporter *reporter); + +GST_VALIDATE_API +void gst_validate_reporter_purge_reports (GstValidateReporter * reporter); +GST_VALIDATE_API +GstPipeline * gst_validate_reporter_get_pipeline (GstValidateReporter * reporter); + +G_END_DECLS +#endif /* _GST_VALIDATE_REPORTER_ */ diff --git a/validate/gst/validate/gst-validate-runner.c b/validate/gst/validate/gst-validate-runner.c new file mode 100644 index 0000000000..13c132e392 --- /dev/null +++ b/validate/gst/validate/gst-validate-runner.c @@ -0,0 +1,985 @@ +/* GStreamer + * + * Copyright (C) 2013-2016 Collabora Ltd. + * Author: Thiago Sousa Santos + * Author: Thibault Saunier + * + * gst-validate-runner.c - Validate Runner class + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "validate.h" +#include "gst-validate-internal.h" +#include "gst-validate-report.h" +#include "gst-validate-monitor-factory.h" +#include "gst-validate-override-registry.h" +#include "gst-validate-runner.h" +#include "gst-validate-reporter.h" +#include "gst-validate-mockdecryptor.h" + +GST_DEBUG_CATEGORY_STATIC (gst_validate_runner_debug); +#undef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT gst_validate_runner_debug + +static gboolean element_created = FALSE; + +/* We create a GstValidateRunner on _init () + * so that we keep backward compatibility when + * the user create a Runner after creating the pipeline + * but the runner was actually already ready to be used. + */ +static GstValidateRunner *first_runner = NULL; + +/** + * SECTION:gst-validate-runner + * @title: GstValidateRunner + * @short_description: Class that runs Gst Validate tests for a pipeline + * + * Allows you to test a pipeline within GstValidate. It is the object where + * all issue reporting is done. + * + * In the tools using GstValidate the only minimal code to be able to monitor + * your pipelines is: + * + * |[ + * GstPipeline *pipeline = gst_pipeline_new ("monitored-pipeline"); + * GstValidateRunner *runner = gst_validate_runner_new (); + * GstValidateMonitor *monitor = gst_validate_monitor_factory_create ( + * GST_OBJECT (pipeline), runner, NULL); + * + * // Run the pipeline and do whatever you want with it + * + * // In that same order + * gst_object_unref (pipeline); + * gst_object_unref (runner); + * gst_object_unref (monitor); + * ]| + * + */ + +struct _GstValidateRunnerPrivate +{ + GMutex mutex; + GList *reports; + GstValidateReportingDetails default_level; + GHashTable *reports_by_type; + + /* A list of PatternLevel */ + GList *report_pattern_levels; + + /* Whether the runner was create with GST_TRACERS=validate or not) */ + gboolean user_created; + + gchar *pipeline_names; + gchar **pipeline_names_strv; + + GList *expected_issues; +}; + +/* Describes the reporting level to apply to a name pattern */ +typedef struct _PatternLevel +{ + GPatternSpec *pattern; + GstValidateReportingDetails level; +} PatternLevel; + +#define GST_VALIDATE_RUNNER_LOCK(r) \ + G_STMT_START { \ + GST_LOG_OBJECT (r, "About to lock %p", &GST_VALIDATE_RUNNER_CAST(r)->priv->mutex); \ + (g_mutex_lock (&GST_VALIDATE_RUNNER_CAST(r)->priv->mutex)); \ + GST_LOG_OBJECT (r, "Acquired lock %p", &GST_VALIDATE_RUNNER_CAST(r)->priv->mutex); \ + } G_STMT_END + +#define GST_VALIDATE_RUNNER_UNLOCK(r) \ + G_STMT_START { \ + GST_LOG_OBJECT (r, "About to unlock %p", &GST_VALIDATE_RUNNER_CAST(r)->priv->mutex); \ + (g_mutex_unlock (&GST_VALIDATE_RUNNER_CAST(r)->priv->mutex)); \ + GST_LOG_OBJECT (r, "Released lock %p", &GST_VALIDATE_RUNNER_CAST(r)->priv->mutex); \ + } G_STMT_END + +#define gst_validate_runner_parent_class parent_class +G_DEFINE_TYPE_WITH_PRIVATE (GstValidateRunner, gst_validate_runner, + GST_TYPE_TRACER); + +/* signals */ +enum +{ + REPORT_ADDED_SIGNAL, + STOPPING_SIGNAL, + /* add more above */ + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_PARAMS, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +static guint _signals[LAST_SIGNAL] = { 0, }; + +static gboolean +gst_validate_runner_should_monitor (GstValidateRunner * self, + GstElement * element) +{ + gint i; + GstValidateMonitor *monitor; + + if (!GST_IS_PIPELINE (element)) { + return FALSE; + } + + if (self->priv->user_created) + return FALSE; + + if (!self->priv->pipeline_names_strv) + return TRUE; + + monitor = gst_validate_get_monitor (G_OBJECT (element)); + + if (monitor) { + GST_ERROR_OBJECT (self, "Pipeline %" GST_PTR_FORMAT " is already" + " monitored by %" GST_PTR_FORMAT " using runner: %" GST_PTR_FORMAT + " NOT monitoring again.", + element, monitor, + gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (monitor))); + } + + for (i = 0; self->priv->pipeline_names_strv[i]; i++) { + if (g_pattern_match_simple (self->priv->pipeline_names_strv[i], + GST_OBJECT_NAME (element))) + return TRUE; + } + + return FALSE; +} + +static void +do_element_new (GstValidateRunner * self, guint64 ts, GstElement * element) +{ + element_created = TRUE; + if (gst_validate_runner_should_monitor (self, element)) { + /* the reference to the monitor is lost */ + gst_validate_monitor_factory_create (GST_OBJECT_CAST (element), self, NULL); + } +} + +static gboolean +_parse_reporting_level (gchar * str, GstValidateReportingDetails * level) +{ + if (!str) + return FALSE; + + /* works in place */ + g_strstrip (str); + + if (g_ascii_isdigit (str[0])) { + unsigned long l; + char *endptr; + l = strtoul (str, &endptr, 10); + if (endptr > str && endptr[0] == 0) { + *level = (GstValidateReportingDetails) l; + } else { + return FALSE; + } + } else if (g_ascii_strcasecmp (str, "smart") == 0) { + *level = GST_VALIDATE_SHOW_SMART; + } else if (g_ascii_strcasecmp (str, "none") == 0) { + *level = GST_VALIDATE_SHOW_NONE; + } else if (g_ascii_strcasecmp (str, "synthetic") == 0) { + *level = GST_VALIDATE_SHOW_SYNTHETIC; + } else if (g_ascii_strcasecmp (str, "subchain") == 0) { + *level = GST_VALIDATE_SHOW_SUBCHAIN; + } else if (g_ascii_strcasecmp (str, "monitor") == 0) { + *level = GST_VALIDATE_SHOW_MONITOR; + } else if (g_ascii_strcasecmp (str, "all") == 0) { + *level = GST_VALIDATE_SHOW_ALL; + } else + return FALSE; + + return TRUE; +} + +static void +_free_report_pattern_level (PatternLevel * pattern_level) +{ + g_pattern_spec_free (pattern_level->pattern); + g_free (pattern_level); +} + +static void +_set_reporting_level_for_name (GstValidateRunner * runner, + const gchar * pattern, GstValidateReportingDetails level) +{ + PatternLevel *pattern_level = g_malloc (sizeof (PatternLevel)); + GPatternSpec *pattern_spec = g_pattern_spec_new (pattern); + + pattern_level->pattern = pattern_spec; + pattern_level->level = level; + + /* Allow the user to single out a pad with the "element-name__pad-name" syntax + */ + if (g_strrstr (pattern, "__")) + runner->priv->report_pattern_levels = + g_list_prepend (runner->priv->report_pattern_levels, pattern_level); + else + runner->priv->report_pattern_levels = + g_list_append (runner->priv->report_pattern_levels, pattern_level); +} + +static void +_replace_double_colons (gchar * word) +{ + while (word) { + word = strstr (word, "::"); + if (word) { + word[0] = '_'; + word[1] = '_'; + } + } +} + +static void +_set_report_levels_from_string (GstValidateRunner * self, const gchar * list) +{ + gchar **split; + gchar **walk; + + g_assert (list); + + GST_DEBUG_OBJECT (self, "setting report levels from string [%s]", list); + + split = g_strsplit (list, ",", 0); + + for (walk = split; *walk; walk++) { + _replace_double_colons (*walk); + if (strchr (*walk, ':')) { + gchar **values = g_strsplit (*walk, ":", 2); + + if (values[0] && values[1]) { + GstValidateReportingDetails level; + + if (_parse_reporting_level (values[1], &level)) + _set_reporting_level_for_name (self, values[0], level); + } + + g_strfreev (values); + } else { + GstValidateReportingDetails level; + + if (_parse_reporting_level (*walk, &level)) + self->priv->default_level = level; + } + } + + g_strfreev (split); +} + +static void +_init_report_levels (GstValidateRunner * self) +{ + const gchar *env; + + env = g_getenv ("GST_VALIDATE_REPORTING_DETAILS"); + if (env) + _set_report_levels_from_string (self, env); +} + +static void +_unref_report_list (gpointer unused, GList * reports, gpointer unused_too) +{ + g_list_free_full (reports, (GDestroyNotify) gst_validate_report_unref); +} + +static void +gst_validate_runner_finalize (GObject * object) +{ + GstValidateRunner *runner = GST_VALIDATE_RUNNER_CAST (object); + + if (!runner->priv->user_created) + gst_validate_runner_exit (runner, TRUE); + + g_list_free_full (runner->priv->reports, + (GDestroyNotify) gst_validate_report_unref); + + g_list_free_full (runner->priv->report_pattern_levels, + (GDestroyNotify) _free_report_pattern_level); + + g_mutex_clear (&runner->priv->mutex); + + g_free (runner->priv->pipeline_names); + g_strfreev (runner->priv->pipeline_names_strv); + + g_hash_table_foreach (runner->priv->reports_by_type, (GHFunc) + _unref_report_list, NULL); + g_hash_table_destroy (runner->priv->reports_by_type); + + G_OBJECT_CLASS (parent_class)->finalize (object); + + if (!runner->priv->user_created) + gst_validate_deinit (); +} + +static GObject * +gst_validate_runner_constructor (GType type, guint n_construct_params, + GObjectConstructParam * construct_params) +{ + GObject *runner = G_OBJECT_CLASS (parent_class)->constructor (type, + n_construct_params, construct_params); + + if (!gst_validate_is_initialized ()) { + first_runner = GST_VALIDATE_RUNNER (runner); + gst_validate_init (); + first_runner = NULL; + + return runner; + } + + return runner; +} + + +static void +gst_validate_runner_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstValidateRunner *runner; + + runner = GST_VALIDATE_RUNNER (object); + switch (prop_id) { + case PROP_PARAMS: + { + g_value_set_string (value, runner->priv->pipeline_names); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_validate_runner_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstValidateRunner *runner; + + runner = GST_VALIDATE_RUNNER (object); + switch (prop_id) { + case PROP_PARAMS: + { + g_free (runner->priv->pipeline_names); + g_strfreev (runner->priv->pipeline_names_strv); + + runner->priv->pipeline_names = g_value_dup_string (value); + if (runner->priv->pipeline_names) + runner->priv->pipeline_names_strv = + g_strsplit (runner->priv->pipeline_names, ",", -1); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_validate_runner_class_init (GstValidateRunnerClass * klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = gst_validate_runner_finalize; + + gobject_class->set_property = gst_validate_runner_set_property; + gobject_class->get_property = gst_validate_runner_get_property; + gobject_class->constructor = gst_validate_runner_constructor; + + properties[PROP_PARAMS] = + g_param_spec_string ("params", "Params", "Extra configuration parameters", + NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, PROP_LAST, properties); + + _signals[REPORT_ADDED_SIGNAL] = + g_signal_new ("report-added", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, + GST_TYPE_VALIDATE_REPORT); + + _signals[STOPPING_SIGNAL] = + g_signal_new ("stopping", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, + NULL, NULL, NULL, G_TYPE_NONE, 0); + + GST_DEBUG_CATEGORY_INIT (gst_validate_runner_debug, "gstvalidaterunner", + GST_DEBUG_FG_YELLOW, "Gst validate runner"); +} + +static void +gst_validate_runner_init (GstValidateRunner * runner) +{ + runner->priv = gst_validate_runner_get_instance_private (runner); + + runner->priv->reports_by_type = g_hash_table_new (g_direct_hash, + g_direct_equal); + + runner->priv->default_level = GST_VALIDATE_SHOW_DEFAULT; + _init_report_levels (runner); + + runner->priv->expected_issues = gst_validate_get_test_file_expected_issues (); + + gst_tracing_register_hook (GST_TRACER (runner), "element-new", + G_CALLBACK (do_element_new)); + + gst_element_register (NULL, GST_MOCKDECRYPTOR_NAME, GST_RANK_MARGINAL, + GST_TYPE_MOCKDECRYPTOR); +} + +/** + * gst_validate_runner_new: + * + * Create a new #GstValidateRunner + * + * Returns: A newly created #GstValidateRunner + */ +GstValidateRunner * +gst_validate_runner_new (void) +{ + GstValidateRunner *runner; + + if (first_runner) { + runner = first_runner; + first_runner = NULL; + } else if (element_created) { + gst_validate_abort + ("Should never create a GstValidateRunner after a GstElement " + "has been created in the same process."); + + return NULL; + } else { + runner = g_object_new (GST_TYPE_VALIDATE_RUNNER, NULL); + runner->priv->user_created = TRUE; + } + + { + GstValidateOverrideRegistry *registry = + gst_validate_override_registry_get (); + GList *all_overrides = + gst_validate_override_registry_get_override_list (registry); + GList *i; + for (i = all_overrides; i; i = i->next) { + GstValidateOverride *override = (GstValidateOverride *) i->data; + gst_validate_reporter_set_runner (GST_VALIDATE_REPORTER (override), + runner); + } + g_list_free (all_overrides); + } + + return runner; +} + +/* + * gst_validate_runner_get_default_reporting_level: + * + * Returns: the default #GstValidateReportingDetails used to output a report. + */ +GstValidateReportingDetails +gst_validate_runner_get_default_reporting_level (GstValidateRunner * runner) +{ + g_return_val_if_fail (GST_IS_VALIDATE_RUNNER (runner), + GST_VALIDATE_SHOW_UNKNOWN); + + return runner->priv->default_level; +} + +/* + * gst_validate_runner_get_reporting_level_for_name: + * + * Returns: the #GstValidateReportingDetails that will be applied for a given name. + * If no pattern was set for such a name, this function will return + * #GST_VALIDATE_SHOW_UNKNOWN, and reporting for that name will + * default to the global reporting level. + */ +GstValidateReportingDetails +gst_validate_runner_get_reporting_level_for_name (GstValidateRunner * runner, + const gchar * name) +{ + GList *tmp; + gchar *fixed_name; + + g_return_val_if_fail (GST_IS_VALIDATE_RUNNER (runner), + GST_VALIDATE_SHOW_UNKNOWN); + + fixed_name = g_strdup (name); + _replace_double_colons (fixed_name); + for (tmp = runner->priv->report_pattern_levels; tmp; tmp = tmp->next) { + PatternLevel *pattern_level = (PatternLevel *) tmp->data; + if (g_pattern_match_string (pattern_level->pattern, fixed_name)) { + g_free (fixed_name); + + return pattern_level->level; + } + } + + g_free (fixed_name); + return GST_VALIDATE_SHOW_UNKNOWN; +} + +static void +synthesize_reports (GstValidateRunner * runner, GstValidateReport * report) +{ + GstValidateIssueId issue_id; + GList *reports; + + issue_id = report->issue->issue_id; + + GST_VALIDATE_RUNNER_LOCK (runner); + reports = + g_hash_table_lookup (runner->priv->reports_by_type, + (gconstpointer) issue_id); + reports = g_list_append (reports, gst_validate_report_ref (report)); + g_hash_table_insert (runner->priv->reports_by_type, (gpointer) issue_id, + reports); + GST_VALIDATE_RUNNER_UNLOCK (runner); +} + +static void +_dot_pipeline (GstValidateReport * report, GstStructure * config) +{ + GstPipeline *pipeline = gst_validate_reporter_get_pipeline (report->reporter); + + if (pipeline) { + gint details = GST_DEBUG_GRAPH_SHOW_ALL; + gchar *reporter_basename = + g_path_get_basename (gst_validate_reporter_get_name (report->reporter)); + report->dotfile_name = + g_strdup_printf ("%" GST_TIME_FORMAT "-validate-report-%s-on-%s-%s", + GST_TIME_ARGS (GST_CLOCK_DIFF (_priv_start_time, + gst_util_get_timestamp ())), + gst_validate_report_level_get_name (report->level), reporter_basename, + g_quark_to_string (report->issue->issue_id)); + + g_free (reporter_basename); + + if (config) + gst_structure_get_int (config, "details", &details); + + GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN (pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, report->dotfile_name); + + gst_object_unref (pipeline); + } + +} + +static void +gst_validate_runner_maybe_dot_pipeline (GstValidateRunner * runner, + GstValidateReport * report) +{ + GList *config; + + if (report->level == GST_VALIDATE_REPORT_LEVEL_CRITICAL || + gst_validate_report_check_abort (report)) { + + _dot_pipeline (report, NULL); + return; + } + + for (config = gst_validate_plugin_get_config (NULL); + config; config = config->next) { + + if (gst_structure_has_name (config->data, "core")) { + GstValidateReportLevel level; + const gchar *level_str, + *action = gst_structure_get_string (config->data, "action"); + + if (g_strcmp0 (action, "dot-pipeline")) + continue; + + level_str = gst_structure_get_string (config->data, "report-level"); + level = level_str ? gst_validate_report_level_from_name (level_str) : + GST_VALIDATE_REPORT_LEVEL_CRITICAL; + + if (level >= report->level) { + _dot_pipeline (report, config->data); + + return; + } + } + } +} + +static gboolean +check_report_expected (GstValidateRunner * runner, GstValidateReport * report) +{ + GList *tmp; + +#define GET_STR(name) gst_structure_get_string (known_issue, name) + + for (tmp = runner->priv->expected_issues; tmp; tmp = tmp->next) { + GstStructure *known_issue = tmp->data; + const gchar *id = GET_STR ("issue-id"); + + if (!id || g_quark_from_string (id) == report->issue->issue_id) { + const gchar *summary = GET_STR ("summary"); + + if (!summary || !g_strcmp0 (summary, report->issue->summary)) { + const gchar *details = GET_STR ("details"); + + if (!details || g_regex_match_simple (details, report->message, 0, 0)) { + const gchar *detected_on = GET_STR ("detected-on"); + + if (!detected_on || !g_strcmp0 (detected_on, report->reporter_name)) { + const gchar *level = GET_STR ("level"); + const gchar *report_level = + gst_validate_report_level_get_name (report->level); + + if (!detected_on || !g_strcmp0 (level, report_level)) { + gboolean is_sometimes; + + if (!gst_structure_get_boolean (known_issue, "sometimes", + &is_sometimes) || !is_sometimes) { + runner->priv->expected_issues = + g_list_remove (runner->priv->expected_issues, known_issue); + gst_structure_free (known_issue); + } + return TRUE; + } + } + } + } + } +#undef GET_STR + } + + return FALSE; +} + +void +gst_validate_runner_add_report (GstValidateRunner * runner, + GstValidateReport * report) +{ + GstValidateReportingDetails details, reporter_details, issue_type_details; + + g_return_if_fail (GST_IS_VALIDATE_RUNNER (runner)); + + if (report->level == GST_VALIDATE_REPORT_LEVEL_IGNORE) + return; + + if (check_report_expected (runner, report)) { + GST_INFO_OBJECT (runner, "Found expected issue: %p", report); + report->level = GST_VALIDATE_REPORT_LEVEL_EXPECTED; + } + + gst_validate_send (json_boxed_serialize (GST_MINI_OBJECT_TYPE (report), + report)); + gst_validate_runner_maybe_dot_pipeline (runner, report); + + details = reporter_details = + gst_validate_reporter_get_reporting_level (report->reporter); + issue_type_details = + gst_validate_runner_get_reporting_level_for_name (runner, + g_quark_to_string (report->issue->issue_id)); + + if (reporter_details == GST_VALIDATE_SHOW_UNKNOWN) + details = issue_type_details; + + /* Let's use our own reporting strategy */ + if (details == GST_VALIDATE_SHOW_UNKNOWN) { + gst_validate_report_set_reporting_level (report, + runner->priv->default_level); + switch (runner->priv->default_level) { + case GST_VALIDATE_SHOW_NONE: + return; + case GST_VALIDATE_SHOW_SMART: + if (!gst_validate_report_check_abort (report) && + report->level != GST_VALIDATE_REPORT_LEVEL_CRITICAL && + !report->trace) { + synthesize_reports (runner, report); + return; + } + break; + case GST_VALIDATE_SHOW_SYNTHETIC: + if (!report->trace) { + synthesize_reports (runner, report); + return; + } + default: + break; + } + } else if (details == GST_VALIDATE_SHOW_NONE) { + GST_DEBUG ("Not reporting."); + return; + } + + GST_VALIDATE_RUNNER_LOCK (runner); + runner->priv->reports = + g_list_append (runner->priv->reports, gst_validate_report_ref (report)); + GST_VALIDATE_RUNNER_UNLOCK (runner); + + g_signal_emit (runner, _signals[REPORT_ADDED_SIGNAL], 0, report); +} + +/** + * gst_validate_runner_get_reports_count: + * @runner: The $GstValidateRunner to get the number of reports from + * + * Get the number of reports present in the runner: + * + * Returns: The number of reports present in the runner. + */ +guint +gst_validate_runner_get_reports_count (GstValidateRunner * runner) +{ + GList *tmp; + guint l; + + g_return_val_if_fail (GST_IS_VALIDATE_RUNNER (runner), 0); + + GST_VALIDATE_RUNNER_LOCK (runner); + l = g_list_length (runner->priv->reports); + for (tmp = runner->priv->reports; tmp; tmp = tmp->next) { + GstValidateReport *report = (GstValidateReport *) tmp->data; + l += g_list_length (report->repeated_reports); + } + l += g_hash_table_size (runner->priv->reports_by_type); + GST_VALIDATE_RUNNER_UNLOCK (runner); + + return l; +} + +/** + * gst_validate_runner_get_reports: + * @runner: The #GstValidateRunner + * + * Return: (element-type GstValidateReport)(transfer full): all the reports + */ +GList * +gst_validate_runner_get_reports (GstValidateRunner * runner) +{ + GList *ret; + + GST_VALIDATE_RUNNER_LOCK (runner); + ret = + g_list_copy_deep (runner->priv->reports, + (GCopyFunc) gst_validate_report_ref, NULL); + GST_VALIDATE_RUNNER_UNLOCK (runner); + + return ret; +} + +static GList * +_do_report_synthesis (GstValidateRunner * runner) +{ + GHashTableIter iter; + GList *reports, *tmp; + gpointer key, value; + GList *criticals = NULL; + + /* Take the lock so the hash table won't be modified while we are iterating + * over it */ + GST_VALIDATE_RUNNER_LOCK (runner); + g_hash_table_iter_init (&iter, runner->priv->reports_by_type); + while (g_hash_table_iter_next (&iter, &key, &value)) { + GstValidateReport *report; + reports = (GList *) value; + + if (!reports) + continue; + + report = (GstValidateReport *) (reports->data); + + gst_validate_report_print_level (report); + gst_validate_report_print_detected_on (report); + + if (report->level == GST_VALIDATE_REPORT_LEVEL_CRITICAL) { + criticals = g_list_append (criticals, report); + gst_validate_report_print_details (report); + } else if (report->issue->flags & GST_VALIDATE_ISSUE_FLAGS_FULL_DETAILS) + gst_validate_report_print_details (report); + + for (tmp = g_list_next (reports); tmp; tmp = tmp->next) { + report = (GstValidateReport *) tmp->data; + gst_validate_report_print_detected_on (report); + + if ((report->level == GST_VALIDATE_REPORT_LEVEL_CRITICAL) || + (report->issue->flags & GST_VALIDATE_ISSUE_FLAGS_FULL_DETAILS)) { + if (report->level == GST_VALIDATE_REPORT_LEVEL_CRITICAL) + criticals = g_list_append (criticals, report); + gst_validate_report_print_details (report); + } + } + report = (GstValidateReport *) (reports->data); + gst_validate_report_print_description (report); + gst_validate_printf (NULL, "\n"); + } + GST_VALIDATE_RUNNER_UNLOCK (runner); + + return criticals; +} + +/** + * gst_validate_runner_printf: + * @runner: The #GstValidateRunner to print all the reports for + * + * Prints all the reports on the terminal or on wherever is set + * in the `GST_VALIDATE_FILE` env variable. + * + * Returns: 0 if no critical error has been found and 18 if a critical + * error has been detected. That return value is usually to be used as + * exit code of the application. + */ +int +gst_validate_runner_printf (GstValidateRunner * runner) +{ + GList *reports, *tmp; + int ret = 0; + GList *criticals = NULL; + + g_return_val_if_fail (GST_IS_VALIDATE_RUNNER (runner), 1); + + criticals = _do_report_synthesis (runner); + reports = gst_validate_runner_get_reports (runner); + for (tmp = reports; tmp; tmp = tmp->next) { + GstValidateReport *report = (GstValidateReport *) tmp->data; + + if (gst_validate_report_should_print (report)) + gst_validate_report_printf (report); + + if (report->level == GST_VALIDATE_REPORT_LEVEL_CRITICAL) { + criticals = g_list_append (criticals, report); + } + } + + if (criticals) { + GList *iter; + g_printerr ("\n\n**Got criticals. Return value set to 18**:\n"); + ret = 18; + for (iter = criticals; iter; iter = iter->next) { + g_printerr (" * critical error %s\n", + ((GstValidateReport *) (iter->data))->message); + } + g_printerr ("\n"); + } + + g_list_free_full (reports, (GDestroyNotify) gst_validate_report_unref); + g_list_free (criticals); + gst_validate_printf (NULL, "Issues found: %u\n", + gst_validate_runner_get_reports_count (runner)); + return ret; +} + +int +gst_validate_runner_exit (GstValidateRunner * runner, gboolean print_result) +{ + gint ret = 0; + GList *tmp, *configs; + + g_return_val_if_fail (GST_IS_VALIDATE_RUNNER (runner), 1); + + g_signal_emit (runner, _signals[STOPPING_SIGNAL], 0); + if (print_result) { + ret = gst_validate_runner_printf (runner); + } else { + GList *tmp; + for (tmp = runner->priv->reports; tmp; tmp = tmp->next) { + GstValidateReport *report = (GstValidateReport *) tmp->data; + if (report->level == GST_VALIDATE_REPORT_LEVEL_CRITICAL) + ret = 18; + } + } + + configs = gst_validate_get_config (NULL); + for (tmp = configs; tmp; tmp = tmp->next) { + if (!gst_structure_has_field (tmp->data, "__n_usages__")) { + gst_validate_error_structure (tmp->data, + "Unused config: '%" GST_PTR_FORMAT "'", tmp->data); + } + } + g_list_free (configs); + + for (tmp = runner->priv->expected_issues; tmp; tmp = tmp->next) { + GstStructure *known_issue = tmp->data; + gboolean is_sometimes; + + if (!gst_structure_get_boolean (known_issue, "sometimes", &is_sometimes) + || !is_sometimes) { + GstStructure *tmpstruct = gst_structure_copy (known_issue); + gst_structure_remove_fields (tmpstruct, "__debug__", "__lineno__", + "__filename__", NULL); + /* Ideally we should report an issue here.. but we do not have a reporter */ + gst_validate_error_structure (known_issue, + "Expected issue didn't happen: '%" GST_PTR_FORMAT "'", tmpstruct); + gst_structure_free (tmpstruct); + } + } + + g_list_free_full (runner->priv->expected_issues, + (GDestroyNotify) gst_structure_free); + runner->priv->expected_issues = NULL; + + return ret; +} + +void +gst_validate_init_runner (void) +{ + if (!first_runner) { + first_runner = g_object_new (GST_TYPE_VALIDATE_RUNNER, NULL); + first_runner->priv->user_created = TRUE; + } /* else the first runner has been created through the GST_TRACERS system */ +} + +void +gst_validate_deinit_runner (void) +{ + g_clear_object (&first_runner); +} + +GstValidateReportingDetails +gst_validate_runner_get_default_reporting_details (GstValidateRunner * runner) +{ + g_return_val_if_fail (GST_IS_VALIDATE_RUNNER (runner), + GST_VALIDATE_SHOW_UNKNOWN); + + return runner->priv->default_level; +} + +#ifdef __GST_VALIDATE_PLUGIN +static gboolean +plugin_init (GstPlugin * plugin) +{ + if (!gst_tracer_register (plugin, "validate", GST_TYPE_VALIDATE_RUNNER)) + return FALSE; + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, validatetracer, + "GStreamer Validate tracers", plugin_init, VERSION, GST_LICENSE, + GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); +#endif /* __GST_VALIDATE_PLUGIN */ diff --git a/validate/gst/validate/gst-validate-runner.h b/validate/gst/validate/gst-validate-runner.h new file mode 100644 index 0000000000..e513803182 --- /dev/null +++ b/validate/gst/validate/gst-validate-runner.h @@ -0,0 +1,104 @@ +/* GStreamer + * Copyright (C) 2013 Thiago Santos + * + * gst-validate-runner.h - Validate Runner class + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_VALIDATE_RUNNER_H__ +#define __GST_VALIDATE_RUNNER_H__ + +#include +#include + +#include + +typedef struct _GstValidateRunner GstValidateRunner; +typedef struct _GstValidateRunnerClass GstValidateRunnerClass; + +#include +#include + +G_BEGIN_DECLS + +#ifndef __GI_SCANNER__ +#define GST_TYPE_VALIDATE_RUNNER (gst_validate_runner_get_type ()) +#define GST_IS_VALIDATE_RUNNER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_VALIDATE_RUNNER)) +#define GST_IS_VALIDATE_RUNNER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_VALIDATE_RUNNER)) +#define GST_VALIDATE_RUNNER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_VALIDATE_RUNNER, GstValidateRunnerClass)) +#define GST_VALIDATE_RUNNER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_VALIDATE_RUNNER, GstValidateRunner)) +#define GST_VALIDATE_RUNNER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_VALIDATE_RUNNER, GstValidateRunnerClass)) +#define GST_VALIDATE_RUNNER_CAST(obj) ((GstValidateRunner*)(obj)) +#define GST_VALIDATE_RUNNER_CLASS_CAST(klass) ((GstValidateRunnerClass*)(klass)) +#endif + +typedef struct _GstValidateRunnerPrivate GstValidateRunnerPrivate; + +/** + * GstValidateRunner: + * + * GStreamer Validate Runner class. + * + * Class that manages a Validate test run for some pipeline + */ +struct _GstValidateRunner { + GstTracer object; + + /* */ + GstValidateRunnerPrivate *priv; +}; + +/** + * GstValidateRunnerClass: + * @parent_class: parent + * + * GStreamer Validate Runner object class. + */ +struct _GstValidateRunnerClass { + GstTracerClass parent_class; +}; + +/* normal GObject stuff */ +GST_VALIDATE_API +GType gst_validate_runner_get_type (void); + +GST_VALIDATE_API +GstValidateRunner * gst_validate_runner_new (void); + +GST_VALIDATE_API +void gst_validate_runner_add_report (GstValidateRunner * runner, GstValidateReport * report); + +GST_VALIDATE_API +guint gst_validate_runner_get_reports_count (GstValidateRunner * runner); +GST_VALIDATE_API +GList * gst_validate_runner_get_reports (GstValidateRunner * runner); + +GST_VALIDATE_API +int gst_validate_runner_printf (GstValidateRunner * runner); +GST_VALIDATE_API +int gst_validate_runner_exit (GstValidateRunner * runner, gboolean print_result); + +GST_VALIDATE_API +GstValidateReportingDetails gst_validate_runner_get_default_reporting_level (GstValidateRunner *runner); +GST_VALIDATE_API +GstValidateReportingDetails gst_validate_runner_get_reporting_level_for_name (GstValidateRunner *runner, + const gchar *name); + +G_END_DECLS + +#endif /* __GST_VALIDATE_RUNNER_H__ */ + diff --git a/validate/gst/validate/gst-validate-scenario.c b/validate/gst/validate/gst-validate-scenario.c new file mode 100644 index 0000000000..414355703a --- /dev/null +++ b/validate/gst/validate/gst-validate-scenario.c @@ -0,0 +1,7309 @@ +/* GStreamer + * + * Copyright (C) 2013 Collabora Ltd. + * Author: Thibault Saunier + * Copyright (C) 2018-2020 Igalia S.L + + * + * gst-validate-scenario.c - Validate Scenario class + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/** + * SECTION:gst-validate-scenario + * @title: GstValidateScenario + * @short_description: A GstValidateScenario represents a set of actions to be executed on a pipeline. + * + * A #GstValidateScenario represents the scenario that will be executed on a #GstPipeline. + * It is basically an ordered list of #GstValidateAction that will be executed during the + * execution of the pipeline. + * + * Possible configurations (see [GST_VALIDATE_CONFIG](gst-validate-environment-variables.md)): + * * scenario-action-execution-interval: Sets the interval in + * milliseconds (1/1000ths of a second), between which actions + * will be executed, setting it to 0 means "execute in idle". + * The default value is 10ms. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include +#include "gst-validate-internal.h" +#include "gst-validate-scenario.h" +#include "gst-validate-reporter.h" +#include "gst-validate-report.h" +#include "gst-validate-utils.h" +#include "gst-validate-internal.h" +#include "validate.h" +#include +#include +#include +#include + +#define GST_VALIDATE_SCENARIO_DIRECTORY "scenarios" + +#define DEFAULT_SEEK_TOLERANCE (1 * GST_MSECOND) /* tolerance seek interval + TODO make it overridable */ + +GST_DEBUG_CATEGORY_STATIC (gst_validate_scenario_debug); +#undef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT gst_validate_scenario_debug + +#define REGISTER_ACTION_TYPE(_tname, _function, _params, _desc, _is_config) G_STMT_START { \ + type = gst_validate_register_action_type ((_tname), "core", (_function), (_params), (_desc), (_is_config)); \ +} G_STMT_END + +#define ACTION_EXPECTED_STREAM_QUARK g_quark_from_static_string ("ACTION_EXPECTED_STREAM_QUARK") + +#define SCENARIO_LOCK(scenario) G_STMT_START { \ + GST_LOG_OBJECT (scenario, "About to lock %p", &scenario->priv->lock); \ + g_mutex_lock(&scenario->priv->lock); \ + GST_LOG_OBJECT (scenario, "Acquired lock %p", &scenario->priv->lock); \ + } G_STMT_END + +#define SCENARIO_UNLOCK(scenario) G_STMT_START { \ + GST_LOG_OBJECT (scenario, "About to unlock %p", &scenario->priv->lock); \ + g_mutex_unlock(&scenario->priv->lock); \ + GST_LOG_OBJECT (scenario, "unlocked %p", &scenario->priv->lock); \ + } G_STMT_END + +#define DECLARE_AND_GET_PIPELINE(s,a) \ + GstElement * pipeline = gst_validate_scenario_get_pipeline (s); \ + if (pipeline == NULL) { \ + GST_VALIDATE_REPORT_ACTION (s, a, SCENARIO_ACTION_EXECUTION_ERROR, \ + "Can't execute a '%s' action after the pipeline " \ + "has been destroyed.", a->type); \ + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; \ + } + +#ifdef G_HAVE_ISO_VARARGS +#define REPORT_UNLESS(condition, errpoint, ...) \ + G_STMT_START { \ + if (!(condition)) { \ + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; \ + gst_validate_report_action(GST_VALIDATE_REPORTER(scenario), action, \ + SCENARIO_ACTION_EXECUTION_ERROR, \ + __VA_ARGS__); \ + goto errpoint; \ + } \ + } \ + G_STMT_END +#elif defined(G_HAVE_GNUC_VARARGS) +#define REPORT_UNLESS(condition, errpoint, args...) \ + G_STMT_START { \ + if (!(condition)) { \ + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; \ + gst_validate_report_action(GST_VALIDATE_REPORTER(scenario), action, \ + SCENARIO_ACTION_EXECUTION_ERROR, ##args); \ + goto errpoint; \ + } \ + } \ + G_STMT_END +#endif + +enum +{ + PROP_0, + PROP_RUNNER, + PROP_HANDLES_STATE, + PROP_EXECUTE_ON_IDLE, + PROP_LAST +}; + +enum +{ + DONE, + ACTION_DONE, + LAST_SIGNAL +}; + +static guint scenario_signals[LAST_SIGNAL] = { 0 }; + +static GList *action_types = NULL; +static void gst_validate_scenario_dispose (GObject * object); +static void gst_validate_scenario_finalize (GObject * object); +static GstValidateActionType *_find_action_type (const gchar * type_name); +static GstValidateExecuteActionReturn +_fill_action (GstValidateScenario * scenario, GstValidateAction * action, + GstStructure * structure, gboolean add_to_lists); +static gboolean _action_set_done (GstValidateAction * action); + +/* GstValidateSinkInformation tracks information for all sinks in the pipeline */ +typedef struct +{ + GstElement *sink; /* The sink element tracked */ + guint32 segment_seqnum; /* The latest segment seqnum. GST_SEQNUM_INVALID if none */ + GstSegment segment; /* The latest segment */ +} GstValidateSinkInformation; + +/* GstValidateSeekInformation tracks: + * * The values used in the seek + * * The seqnum used in the seek event + * * The validate action to which it relates + */ +typedef struct +{ + guint32 seqnum; /* seqnum of the seek event */ + + /* Seek values */ + gdouble rate; + GstFormat format; + GstSeekFlags flags; + GstSeekType start_type, stop_type; + gint64 start, stop; + + /* The action corresponding to this seek */ + GstValidateAction *action; +} GstValidateSeekInformation; + +/* GstValidateScenario is not really thread safe and + * everything should be done from the thread GstValidate + * was inited from, unless stated otherwise. + */ +struct _GstValidateScenarioPrivate +{ + GstBus *bus; + GstValidateRunner *runner; + gboolean execute_on_idle; + + GMutex lock; + + GList *actions; + GList *non_blocking_running_actions; /* MT safe. Protected with SCENARIO_LOCK */ + GList *on_addition_actions; /* MT safe. Protected with SCENARIO_LOCK */ + + gboolean needs_playback_parsing; + + GList *sinks; /* List of GstValidateSinkInformation */ + GList *seeks; /* List of GstValidateSeekInformation */ + + /* Seek currently applied (set when all sinks received segment with + * an identical seqnum and there is a matching pending seek). + * do not free, should always be present in the seek list above */ + GstValidateSeekInformation *current_seek; + /* Current unified seqnum. Set when all sinks received segment with + * an identical seqnum, even if there wasn't a matching pending seek + */ + guint32 current_seqnum; + + /* List of action that need parsing when reaching ASYNC_DONE + * most probably to be able to query duration */ + + /* seek_flags : + * * Only set for seek actions, and only if seek succeeded + * * Only Used in _check_position() + * FIXME : Just use the seek information */ + GstSeekFlags seek_flags; + GstFormat seek_format; + + /* segment_start/segment_stop : + * * Set : from seek values + * * Read : In _check_position() + * FIXME : Just use the current seek information */ + GstClockTime segment_start; + GstClockTime segment_stop; + + /* Always initialized to a default value + * FIXME : Is it still needed with the new seeking validation system ? */ + GstClockTime seek_pos_tol; + + /* If we seeked in paused the position should be exactly what + * the seek value was (if accurate) */ + gboolean seeked_in_pause; + + guint num_actions; + + gboolean handles_state; + + guint execute_actions_source_id; /* MT safe. Protect with SCENARIO_LOCK */ + guint wait_id; + guint signal_handler_id; /* MT safe. Protect with SCENARIO_LOCK */ + guint action_execution_interval; + + /* Name of message the wait action is waiting for */ + GstValidateAction *wait_message_action; + + gboolean buffering; + + gboolean got_eos; + gboolean changing_state; + gboolean needs_async_done; + gboolean ignore_eos; + GstState target_state; + + GList *overrides; + + gchar *pipeline_name; + GstClockTime max_latency; + gint dropped; + gint max_dropped; + + /* 'switch-track action' currently waiting for + * GST_MESSAGE_STREAMS_SELECTED to be completed. */ + GstValidateAction *pending_switch_track; + + GstStructure *vars; + + GWeakRef ref_pipeline; + + GstTestClock *clock; + guint segments_needed; + + GMainContext *context; +}; + +typedef struct KeyFileGroupName +{ + GKeyFile *kf; + gchar *group_name; +} KeyFileGroupName; + +#define NOT_KF_AFTER_FORCE_KF_EVT_TOLERANCE 1 + +static void +gst_validate_sink_information_free (GstValidateSinkInformation * info) +{ + gst_object_unref (info->sink); + g_free (info); +} + +static void +gst_validate_seek_information_free (GstValidateSeekInformation * info) +{ + gst_validate_action_unref (info->action); + g_free (info); +} + +static GstValidateInterceptionReturn +gst_validate_scenario_intercept_report (GstValidateReporter * reporter, + GstValidateReport * report) +{ + GList *tmp; + + for (tmp = GST_VALIDATE_SCENARIO (reporter)->priv->overrides; tmp; + tmp = tmp->next) { + GstValidateOverride *override = (GstValidateOverride *) tmp->data; + report->level = + gst_validate_override_get_severity (override, + gst_validate_issue_get_id (report->issue), report->level); + } + + return GST_VALIDATE_REPORTER_REPORT; +} + +/** + * gst_validate_scenario_get_pipeline: + * @scenario: The scenario to retrieve a pipeline from + * + * Returns: (transfer full): The #GstPipeline the scenario is running + * against + */ +GstElement * +gst_validate_scenario_get_pipeline (GstValidateScenario * scenario) +{ + return g_weak_ref_get (&scenario->priv->ref_pipeline); +} + +static GstPipeline * +_get_pipeline (GstValidateReporter * reporter) +{ + return + GST_PIPELINE_CAST (gst_validate_scenario_get_pipeline + (GST_VALIDATE_SCENARIO (reporter))); +} + +static void +_reporter_iface_init (GstValidateReporterInterface * iface) +{ + iface->intercept_report = gst_validate_scenario_intercept_report; + iface->get_pipeline = _get_pipeline; +} + +G_DEFINE_TYPE_WITH_CODE (GstValidateScenario, gst_validate_scenario, + GST_TYPE_OBJECT, G_ADD_PRIVATE (GstValidateScenario) + G_IMPLEMENT_INTERFACE (GST_TYPE_VALIDATE_REPORTER, _reporter_iface_init)); + +/* GstValidateAction implementation */ +static GType _gst_validate_action_type = 0; + +struct _GstValidateActionPrivate +{ + GstStructure *main_structure; + GstValidateExecuteActionReturn state; /* Actually ActionState */ + gboolean printed; + gboolean executing_last_subaction; + gboolean subaction_level; + gboolean optional; + + GstClockTime execution_time; + GstClockTime execution_duration; + GstClockTime timeout; + + GWeakRef scenario; + gboolean needs_playback_parsing; + gboolean pending_set_done; + + GMainContext *context; +}; + +static JsonNode * +gst_validate_action_serialize (GstValidateAction * action) +{ + JsonNode *node = json_node_alloc (); + JsonObject *jreport = json_object_new (); + gchar *action_args = gst_structure_to_string (action->structure); + + json_object_set_string_member (jreport, "type", "action"); + json_object_set_string_member (jreport, "action-type", action->type); + json_object_set_int_member (jreport, "playback-time", + (gint64) action->playback_time); + json_object_set_string_member (jreport, "args", action_args); + g_free (action_args); + + node = json_node_init_object (node, jreport); + json_object_unref (jreport); + + return node; +} + +GType +gst_validate_action_get_type (void) +{ + if (_gst_validate_action_type == 0) { + _gst_validate_action_type = + g_boxed_type_register_static (g_intern_static_string + ("GstValidateAction"), (GBoxedCopyFunc) gst_validate_action_ref, + (GBoxedFreeFunc) gst_validate_action_unref); + + json_boxed_register_serialize_func (_gst_validate_action_type, + JSON_NODE_OBJECT, + (JsonBoxedSerializeFunc) gst_validate_action_serialize); + } + + return _gst_validate_action_type; +} + +static gboolean execute_next_action (GstValidateScenario * scenario); +static gboolean +gst_validate_scenario_load (GstValidateScenario * scenario, + const gchar * scenario_name); + +static GstValidateAction * +_action_copy (GstValidateAction * act) +{ + GstValidateScenario *scenario = gst_validate_action_get_scenario (act); + GstValidateAction *copy = gst_validate_action_new (scenario, + _find_action_type (act->type), NULL, FALSE); + + gst_object_unref (scenario); + + if (act->structure) { + copy->structure = gst_structure_copy (act->structure); + copy->type = gst_structure_get_name (copy->structure); + if (!(act->name = gst_structure_get_string (copy->structure, "name"))) + act->name = ""; + } + + if (act->priv->main_structure) + copy->priv->main_structure = gst_structure_copy (act->priv->main_structure); + + copy->action_number = act->action_number; + copy->playback_time = act->playback_time; + copy->priv->timeout = act->priv->timeout; + GST_VALIDATE_ACTION_LINENO (copy) = GST_VALIDATE_ACTION_LINENO (act); + GST_VALIDATE_ACTION_FILENAME (copy) = + g_strdup (GST_VALIDATE_ACTION_FILENAME (act)); + GST_VALIDATE_ACTION_DEBUG (copy) = g_strdup (GST_VALIDATE_ACTION_DEBUG (act)); + GST_VALIDATE_ACTION_N_REPEATS (copy) = GST_VALIDATE_ACTION_N_REPEATS (act); + GST_VALIDATE_ACTION_RANGE_NAME (copy) = GST_VALIDATE_ACTION_RANGE_NAME (act); + + return copy; +} + +const gchar * +gst_validate_action_return_get_name (GstValidateActionReturn r) +{ + switch (r) { + case GST_VALIDATE_EXECUTE_ACTION_ERROR: + return "ERROR"; + case GST_VALIDATE_EXECUTE_ACTION_OK: + return "OK"; + case GST_VALIDATE_EXECUTE_ACTION_ASYNC: + return "ASYNC"; + case GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING: + return "NON-BLOCKING"; + case GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED: + return "ERROR(reported)"; + case GST_VALIDATE_EXECUTE_ACTION_IN_PROGRESS: + return "IN_PROGRESS"; + case GST_VALIDATE_EXECUTE_ACTION_NONE: + return "NONE"; + case GST_VALIDATE_EXECUTE_ACTION_DONE: + return "DONE"; + } + g_assert_not_reached (); + return "???"; +} + +static void +_action_free (GstValidateAction * action) +{ + if (action->structure) + gst_structure_free (action->structure); + + if (action->priv->main_structure) + gst_structure_free (action->priv->main_structure); + + g_weak_ref_clear (&action->priv->scenario); + g_free (GST_VALIDATE_ACTION_FILENAME (action)); + g_free (GST_VALIDATE_ACTION_DEBUG (action)); + + g_slice_free (GstValidateActionPrivate, action->priv); + g_slice_free (GstValidateAction, action); +} + +static void +gst_validate_action_init (GstValidateAction * action) +{ + gst_mini_object_init (((GstMiniObject *) action), 0, + _gst_validate_action_type, (GstMiniObjectCopyFunction) _action_copy, NULL, + (GstMiniObjectFreeFunction) _action_free); + + action->priv = g_slice_new0 (GstValidateActionPrivate); + + g_weak_ref_init (&action->priv->scenario, NULL); +} + +GstValidateAction * +gst_validate_action_ref (GstValidateAction * action) +{ + return (GstValidateAction *) gst_mini_object_ref (GST_MINI_OBJECT (action)); +} + +void +gst_validate_action_unref (GstValidateAction * action) +{ + gst_mini_object_unref (GST_MINI_OBJECT (action)); +} + +/** + * gst_validate_action_new: + * @scenario: (allow-none): The scenario executing the action + * @action_type: The action type + * @structure: The structure containing the action arguments + * @add_to_lists: Weather the action should be added to the scenario action list + * + * Returns: A newly created #GstValidateAction + */ +GstValidateAction * +gst_validate_action_new (GstValidateScenario * scenario, + GstValidateActionType * action_type, GstStructure * structure, + gboolean add_to_lists) +{ + GstValidateAction *action = g_slice_new0 (GstValidateAction); + + g_assert (action_type); + + gst_validate_action_init (action); + action->playback_time = GST_CLOCK_TIME_NONE; + action->priv->timeout = GST_CLOCK_TIME_NONE; + action->priv->state = GST_VALIDATE_EXECUTE_ACTION_NONE; + action->type = action_type->name; + action->repeat = -1; + + g_weak_ref_set (&action->priv->scenario, scenario); + if (structure) { + gchar *filename = NULL; + gst_structure_get (structure, + "__lineno__", G_TYPE_INT, &GST_VALIDATE_ACTION_LINENO (action), + "__filename__", G_TYPE_STRING, &filename, + "__debug__", G_TYPE_STRING, &GST_VALIDATE_ACTION_DEBUG (action), NULL); + if (filename) { + GST_VALIDATE_ACTION_FILENAME (action) = + g_filename_display_basename (filename); + g_free (filename); + } + gst_structure_remove_fields (structure, "__lineno__", "__filename__", + "__debug__", NULL); + action->priv->state = + _fill_action (scenario, action, structure, add_to_lists); + } + + return action; +} + +gboolean +_action_check_and_set_printed (GstValidateAction * action) +{ + if (action->priv->printed == FALSE) { + gst_validate_send (json_boxed_serialize (GST_MINI_OBJECT_TYPE + (action), action)); + + action->priv->printed = TRUE; + + return FALSE; + } + + return TRUE; +} + +gint +gst_validate_action_get_level (GstValidateAction * action) +{ + return action->priv->subaction_level; +} + +/* GstValidateActionType implementation */ +GType _gst_validate_action_type_type; +GST_DEFINE_MINI_OBJECT_TYPE (GstValidateActionType, gst_validate_action_type); +static GstValidateActionType *gst_validate_action_type_new (void); + +struct _GstValidateActionTypePrivate +{ + gint n_calls; +}; + +static void +_action_type_free (GstValidateActionType * type) +{ + g_free (type->parameters); + g_free (type->description); + g_free (type->name); + g_free (type->implementer_namespace); + g_free (type->priv); + + if (type->overriden_type) + gst_mini_object_unref (GST_MINI_OBJECT (type->overriden_type)); + + g_slice_free (GstValidateActionType, type); +} + +static void +gst_validate_action_type_init (GstValidateActionType * type) +{ + type->priv = g_new0 (GstValidateActionTypePrivate, 1); + + gst_mini_object_init ((GstMiniObject *) type, 0, + _gst_validate_action_type_type, NULL, NULL, + (GstMiniObjectFreeFunction) _action_type_free); +} + +GstValidateActionType * +gst_validate_action_type_new (void) +{ + GstValidateActionType *type = g_slice_new0 (GstValidateActionType); + + gst_validate_action_type_init (type); + + return type; +} + +static GstValidateActionType * +_find_action_type (const gchar * type_name) +{ + GList *tmp; + + for (tmp = action_types; tmp; tmp = tmp->next) { + GstValidateActionType *atype = (GstValidateActionType *) tmp->data; + if (g_strcmp0 (atype->name, type_name) == 0) + return atype; + } + + return NULL; +} + +static void +_update_well_known_vars (GstValidateScenario * scenario) +{ + gint64 duration, position; + gdouble dduration, dposition; + GstElement *pipeline = gst_validate_scenario_get_pipeline (scenario); + + gst_structure_remove_fields (scenario->priv->vars, "position", "duration", + NULL); + + if (!pipeline) + return; + + if (!gst_element_query_duration (pipeline, GST_FORMAT_TIME, &duration) || + !GST_CLOCK_TIME_IS_VALID (duration)) { + GstValidateMonitor *monitor = + (GstValidateMonitor *) (g_object_get_data ((GObject *) + pipeline, "validate-monitor")); + GST_INFO_OBJECT (scenario, + "Could not query duration. Trying to get duration from media-info"); + if (monitor && monitor->media_descriptor) + duration = + gst_validate_media_descriptor_get_duration + (monitor->media_descriptor); + } + + if (!GST_CLOCK_TIME_IS_VALID (duration)) + dduration = G_MAXDOUBLE; + else + dduration = ((double) duration / GST_SECOND); + + gst_structure_set (scenario->priv->vars, "duration", G_TYPE_DOUBLE, dduration, + NULL); + if (gst_element_query_position (pipeline, GST_FORMAT_TIME, &position)) { + + if (!GST_CLOCK_TIME_IS_VALID (position)) + dposition = G_MAXDOUBLE; + else + dposition = ((double) position / GST_SECOND); + + gst_structure_set (scenario->priv->vars, "position", G_TYPE_DOUBLE, + dposition, NULL); + } else { + GST_WARNING_OBJECT (scenario, "Could not query position"); + } +} + +static GstElement *_get_target_element (GstValidateScenario * scenario, + GstValidateAction * action); + +static GstObject * +_get_target_object_property (GstValidateScenario * scenario, + GstValidateAction * action, const gchar * property_path, + GParamSpec ** pspec) +{ + GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK; + gchar **elem_pad_name = NULL; + gchar **object_prop_name = NULL; + const gchar *elemname; + const gchar *padname = NULL; + GstObject *target = NULL; + gint i; + + elem_pad_name = g_strsplit (property_path, ".", 2); + object_prop_name = + g_strsplit (elem_pad_name[1] ? elem_pad_name[1] : elem_pad_name[0], "::", + -1); + REPORT_UNLESS (object_prop_name[1], err, + "Property specification %s is missing a `::propename` part", + property_path); + + if (elem_pad_name[1]) { + elemname = elem_pad_name[0]; + padname = object_prop_name[0]; + } else { + elemname = object_prop_name[0]; + } + + gst_structure_set (action->structure, "target-element-name", G_TYPE_STRING, + elemname, NULL); + + target = (GstObject *) _get_target_element (scenario, action); + gst_structure_remove_field (action->structure, "target-element-name"); + REPORT_UNLESS (target, err, "Target element with given name (%s) not found", + elemname); + + if (padname) { + gboolean done = FALSE; + GstIterator *it = gst_element_iterate_pads (GST_ELEMENT (target)); + GValue v = G_VALUE_INIT; + + gst_clear_object (&target); + while (!done) { + switch (gst_iterator_next (it, &v)) { + case GST_ITERATOR_OK:{ + GstPad *pad = g_value_get_object (&v); + gchar *name = gst_object_get_name (GST_OBJECT (pad)); + + if (!g_strcmp0 (name, padname)) { + done = TRUE; + gst_clear_object (&target); + + target = gst_object_ref (pad); + } + g_free (name); + g_value_reset (&v); + break; + } + case GST_ITERATOR_RESYNC: + gst_iterator_resync (it); + break; + case GST_ITERATOR_ERROR: + case GST_ITERATOR_DONE: + done = TRUE; + } + } + + gst_iterator_free (it); + } + REPORT_UNLESS (target, err, "Could not find pad: %s::%s", elemname, padname); + + for (i = 1;;) { + const gchar *propname = object_prop_name[i]; + + *pspec = + g_object_class_find_property (G_OBJECT_GET_CLASS (target), propname); + + REPORT_UNLESS (*pspec, err, + "Object %" GST_PTR_FORMAT " doesn't have a property call '%s'", target, + propname); + + if (!object_prop_name[++i]) + break; + + REPORT_UNLESS (g_type_is_a ((*pspec)->owner_type, G_TYPE_OBJECT), err, + "Property: %" GST_PTR_FORMAT "::%s not a GObject, can't use it.", + target, propname); + + g_object_get (target, propname, &target, NULL); + REPORT_UNLESS (target, err, + "Property: %" GST_PTR_FORMAT "::%s is NULL can't get %s.", + target, propname, object_prop_name[i + 1]); + } + + REPORT_UNLESS (res == GST_VALIDATE_EXECUTE_ACTION_OK, err, "Something fishy"); + +done: + g_strfreev (elem_pad_name); + g_strfreev (object_prop_name); + return target; + +err: + gst_clear_object (&target); + goto done; +} + +static gboolean +_set_variable_func (const gchar * name, double *value, gpointer user_data) +{ + GstValidateScenario *scenario = (GstValidateScenario *) user_data; + + if (!gst_structure_get_double (scenario->priv->vars, name, value)) + return FALSE; + + return TRUE; +} + +/* Check that @list doesn't contain any non-optional actions */ +static gboolean +actions_list_is_done (GList * list) +{ + GList *l; + + for (l = list; l != NULL; l = g_list_next (l)) { + GstValidateAction *action = l->data; + + if (!action->priv->optional) + return FALSE; + } + + return TRUE; +} + +static void +_check_scenario_is_done (GstValidateScenario * scenario) +{ + SCENARIO_LOCK (scenario); + if (actions_list_is_done (scenario->priv->actions) && + actions_list_is_done (scenario->priv->non_blocking_running_actions) && + actions_list_is_done (scenario->priv->on_addition_actions)) { + SCENARIO_UNLOCK (scenario); + + g_signal_emit (scenario, scenario_signals[DONE], 0); + } else { + SCENARIO_UNLOCK (scenario); + } +} + +static void +_reset_sink_information (GstValidateSinkInformation * sinkinfo) +{ + sinkinfo->segment_seqnum = GST_SEQNUM_INVALID; + gst_segment_init (&sinkinfo->segment, GST_FORMAT_UNDEFINED); +} + +/** + * gst_validate_action_get_clocktime: + * @scenario: The #GstValidateScenario from which to get a time + * for a parameter of an action + * @action: The action from which to retrieve the time for @name + * parameter. + * @name: The name of the parameter for which to retrieve a time + * @retval: (out): The return value for the wanted time + * + * Get a time value for the @name parameter of an action. This + * method should be called to retrieve and compute a timed value of a given + * action. It will first try to retrieve the value as a double, + * then get it as a string and execute any formula taking into account + * the 'position' and 'duration' variables. And it will always convert that + * value to a GstClockTime. + * + * Returns: %TRUE if the time value could be retrieved/computed or %FALSE otherwise + */ +gboolean +gst_validate_action_get_clocktime (GstValidateScenario * scenario, + GstValidateAction * action, const gchar * name, GstClockTime * retval) +{ + + if (!gst_structure_has_field (action->structure, name)) + return FALSE; + + if (!gst_validate_utils_get_clocktime (action->structure, name, retval)) { + gdouble val; + gchar *error = NULL, *strval; + const gchar *tmpvalue = gst_structure_get_string (action->structure, name); + + if (!tmpvalue) { + GST_INFO_OBJECT (scenario, "Could not find %s (%" GST_PTR_FORMAT ")", + name, action->structure); + return -1; + } + + _update_well_known_vars (scenario); + strval = + gst_validate_replace_variables_in_string (action, scenario->priv->vars, + tmpvalue, GST_VALIDATE_STRUCTURE_RESOLVE_VARIABLES_ALL); + if (!strval) + return FALSE; + + val = + gst_validate_utils_parse_expression (strval, _set_variable_func, + scenario, &error); + if (error) { + GST_WARNING ("Error while parsing %s: %s (%" GST_PTR_FORMAT ")", + strval, error, scenario->priv->vars); + g_free (error); + g_free (strval); + + return FALSE; + } else if (val == -1.0) { + *retval = GST_CLOCK_TIME_NONE; + } else { + gint n, d; + + gst_util_double_to_fraction (val, &n, &d); + *retval = gst_util_uint64_scale_int_round (n, GST_SECOND, d); + } + gst_structure_set (action->structure, name, G_TYPE_UINT64, *retval, NULL); + g_free (strval); + + return TRUE; + } + + return TRUE; +} + +/* WITH SCENARIO LOCK TAKEN */ +static GstValidateSinkInformation * +_find_sink_information (GstValidateScenario * scenario, GstElement * sink) +{ + GList *tmp; + + for (tmp = scenario->priv->sinks; tmp; tmp = tmp->next) { + GstValidateSinkInformation *sink_info = + (GstValidateSinkInformation *) tmp->data; + if (sink_info->sink == sink) + return sink_info; + } + return NULL; +} + +/* WITH SCENARIO LOCK TAKEN */ +static GstValidateSeekInformation * +_find_seek_information (GstValidateScenario * scenario, guint32 seqnum) +{ + GList *tmp; + + for (tmp = scenario->priv->seeks; tmp; tmp = tmp->next) { + GstValidateSeekInformation *seek_info = + (GstValidateSeekInformation *) tmp->data; + if (seek_info->seqnum == seqnum) + return seek_info; + } + + return NULL; +} + +/* WITH SCENARIO LOCK TAKEN */ +static void +_validate_sink_information (GstValidateScenario * scenario) +{ + GList *tmp; + gboolean all_sinks_ready = TRUE; + gboolean identical_seqnum = TRUE; + gboolean transitioning = FALSE; + guint32 common_seqnum = GST_SEQNUM_INVALID; + guint32 next_seqnum = GST_SEQNUM_INVALID; + GstValidateSeekInformation *seek_info; + + if (scenario->priv->seeks) + /* If we have a pending seek, get the expected seqnum to + * figure out whether we are transitioning to a seek */ + next_seqnum = + ((GstValidateSeekInformation *) scenario->priv->seeks->data)->seqnum; + + GST_LOG_OBJECT (scenario, "next_seqnum %" G_GUINT32_FORMAT, next_seqnum); + + for (tmp = scenario->priv->sinks; tmp; tmp = tmp->next) { + GstValidateSinkInformation *sink_info = + (GstValidateSinkInformation *) tmp->data; + GST_DEBUG_OBJECT (sink_info->sink, + "seqnum:%" G_GUINT32_FORMAT " segment:%" GST_SEGMENT_FORMAT, + sink_info->segment_seqnum, &sink_info->segment); + if (sink_info->segment_seqnum == GST_SEQNUM_INVALID) + all_sinks_ready = FALSE; + else if (sink_info->segment.format == GST_FORMAT_TIME) { + /* Are we in the middle of switching segments (from the current + * one, or to the next week) ? */ + if (sink_info->segment_seqnum == scenario->priv->current_seqnum || + sink_info->segment_seqnum == next_seqnum) + transitioning = TRUE; + + /* We are only interested in sinks that handle TIME segments */ + if (common_seqnum == GST_SEQNUM_INVALID) + common_seqnum = sink_info->segment_seqnum; + else if (common_seqnum != sink_info->segment_seqnum) { + identical_seqnum = FALSE; + } + } + } + + /* If not all sinks have received a segment, just return */ + if (!all_sinks_ready) + return; + + GST_FIXME_OBJECT (scenario, + "All sinks have valid segment. identical_seqnum:%d transitioning:%d seqnum:%" + G_GUINT32_FORMAT " (current:%" G_GUINT32_FORMAT ") seeks:%p", + identical_seqnum, transitioning, common_seqnum, + scenario->priv->current_seqnum, scenario->priv->seeks); + + if (!identical_seqnum) { + /* If all sinks received a segment *and* there is a pending seek *and* there + * wasn't one previously, we definitely have a failure */ + if (!transitioning && scenario->priv->current_seek == NULL + && scenario->priv->seeks) { + GST_VALIDATE_REPORT (scenario, EVENT_SEEK_INVALID_SEQNUM, + "Not all segments from a given seek have the same seqnum"); + return; + } + /* Otherwise we're either doing the initial preroll (without seek) + * or we are in the middle of switching to another seek */ + return; + } + + /* Now check if we have seek data related to that seqnum */ + seek_info = _find_seek_information (scenario, common_seqnum); + + if (seek_info && seek_info != scenario->priv->current_seek) { + GST_DEBUG_OBJECT (scenario, "Found a corresponding seek !"); + /* Updating values */ + /* FIXME : Check segment values if needed ! */ + /* FIXME : Non-flushing seek, validate here */ + if (seek_info->start_type == GST_SEEK_TYPE_SET) + scenario->priv->segment_start = seek_info->start; + if (seek_info->stop_type == GST_SEEK_TYPE_SET) + scenario->priv->segment_stop = seek_info->stop; + if (scenario->priv->target_state == GST_STATE_PAUSED) + scenario->priv->seeked_in_pause = TRUE; + SCENARIO_UNLOCK (scenario); + /* If it's a non-flushing seek, validate it here + * otherwise we will do it when the async_done is received */ + if (!(seek_info->flags & GST_SEEK_FLAG_FLUSH)) + gst_validate_action_set_done (seek_info->action); + SCENARIO_LOCK (scenario); + } + /* We always set the current_seek. Can be NULL if no matching */ + scenario->priv->current_seek = seek_info; + scenario->priv->current_seqnum = common_seqnum; +} + +/** + * gst_validate_scenario_execute_seek: + * @scenario: The #GstValidateScenario for which to execute a seek action + * @action: The seek action to execute + * @rate: The playback rate of the seek + * @format: The #GstFormat of the seek + * @flags: The #GstSeekFlags of the seek + * @start_type: The #GstSeekType of the start value of the seek + * @start: The start time of the seek + * @stop_type: The #GstSeekType of the stop value of the seek + * @stop: The stop time of the seek + * + * Executes a seek event on the scenario's pipeline. You should always use + * this method when you want to execute a seek inside a new action type + * so that the scenario state is updated taking into account that seek. + * + * For more information you should have a look at #gst_event_new_seek + * + * Returns: %TRUE if the seek could be executed, %FALSE otherwise + */ +GstValidateExecuteActionReturn +gst_validate_scenario_execute_seek (GstValidateScenario * scenario, + GstValidateAction * action, gdouble rate, GstFormat format, + GstSeekFlags flags, GstSeekType start_type, GstClockTime start, + GstSeekType stop_type, GstClockTime stop) +{ + GstEvent *seek; + GstValidateSeekInformation *seek_info; + + GstValidateExecuteActionReturn ret = GST_VALIDATE_EXECUTE_ACTION_ASYNC; + GstValidateScenarioPrivate *priv = scenario->priv; + DECLARE_AND_GET_PIPELINE (scenario, action); + + seek = gst_event_new_seek (rate, format, flags, start_type, start, + stop_type, stop); + + if (format != GST_FORMAT_TIME && format != GST_FORMAT_DEFAULT) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Trying to seek in format %d, but not support yet!", format); + } + + seek_info = g_new0 (GstValidateSeekInformation, 1); + seek_info->seqnum = GST_EVENT_SEQNUM (seek); + seek_info->rate = rate; + seek_info->format = format; + seek_info->flags = flags; + seek_info->start = start; + seek_info->stop = stop; + seek_info->start_type = start_type; + seek_info->stop_type = stop_type; + seek_info->action = gst_validate_action_ref (action); + + SCENARIO_LOCK (scenario); + priv->seeks = g_list_append (priv->seeks, seek_info); + SCENARIO_UNLOCK (scenario); + + gst_event_ref (seek); + if (gst_element_send_event (pipeline, seek)) { + priv->seek_flags = flags; + priv->seek_format = format; + } else { + switch (format) { + case GST_FORMAT_TIME: + GST_VALIDATE_REPORT_ACTION (scenario, action, EVENT_SEEK_NOT_HANDLED, + "Could not execute seek: '(position %" GST_TIME_FORMAT + "), %s (num %u, missing repeat: %i), seeking to: %" GST_TIME_FORMAT + " stop: %" GST_TIME_FORMAT " Rate %lf'", + GST_TIME_ARGS (action->playback_time), action->name, + action->action_number, action->repeat, GST_TIME_ARGS (start), + GST_TIME_ARGS (stop), rate); + break; + default: + { + gchar *format_str = g_enum_to_string (GST_TYPE_FORMAT, format); + + GST_VALIDATE_REPORT_ACTION (scenario, action, EVENT_SEEK_NOT_HANDLED, + "Could not execute seek in format %s '(position %" GST_TIME_FORMAT + "), %s (num %u, missing repeat: %i), seeking to: %" G_GINT64_FORMAT + " stop: %" G_GINT64_FORMAT " Rate %lf'", format_str, + GST_TIME_ARGS (action->playback_time), action->name, + action->action_number, action->repeat, start, stop, rate); + g_free (format_str); + break; + } + } + SCENARIO_LOCK (scenario); + priv->seeks = g_list_remove (priv->seeks, seek_info); + SCENARIO_UNLOCK (scenario); + + gst_validate_seek_information_free (seek_info); + ret = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + gst_event_unref (seek); + gst_object_unref (pipeline); + + return ret; +} + +static gint +_execute_seek (GstValidateScenario * scenario, GstValidateAction * action) +{ + const char *str_format, *str_flags, *str_start_type, *str_stop_type; + + gdouble rate = 1.0; + guint format = GST_FORMAT_TIME; + GstSeekFlags flags = 0; + guint start_type = GST_SEEK_TYPE_SET; + GstClockTime start; + guint stop_type = GST_SEEK_TYPE_SET; + GstClockTime stop = GST_CLOCK_TIME_NONE; + + if (!gst_validate_action_get_clocktime (scenario, action, "start", &start)) + return GST_VALIDATE_EXECUTE_ACTION_ERROR; + + gst_structure_get_double (action->structure, "rate", &rate); + if ((str_format = gst_structure_get_string (action->structure, "format"))) + gst_validate_utils_enum_from_str (GST_TYPE_FORMAT, str_format, &format); + + if ((str_start_type = + gst_structure_get_string (action->structure, "start_type"))) + gst_validate_utils_enum_from_str (GST_TYPE_SEEK_TYPE, str_start_type, + &start_type); + + if ((str_stop_type = + gst_structure_get_string (action->structure, "stop_type"))) + gst_validate_utils_enum_from_str (GST_TYPE_SEEK_TYPE, str_stop_type, + &stop_type); + + if ((str_flags = gst_structure_get_string (action->structure, "flags"))) + flags = gst_validate_utils_flags_from_str (GST_TYPE_SEEK_FLAGS, str_flags); + + gst_validate_action_get_clocktime (scenario, action, "stop", &stop); + + return gst_validate_scenario_execute_seek (scenario, action, rate, format, + flags, start_type, start, stop_type, stop); +} + +static gboolean +_pause_action_restore_playing (GstValidateScenario * scenario) +{ + GstElement *pipeline = gst_validate_scenario_get_pipeline (scenario); + + if (!pipeline) { + GST_ERROR_OBJECT (scenario, "No pipeline set anymore!"); + + return FALSE; + } + + gst_validate_printf (scenario, "Back to playing\n"); + + if (gst_element_set_state (pipeline, GST_STATE_PLAYING) == + GST_STATE_CHANGE_FAILURE) { + GST_VALIDATE_REPORT (scenario, STATE_CHANGE_FAILURE, + "Failed to set state to playing"); + scenario->priv->target_state = GST_STATE_PLAYING; + } + + gst_object_unref (pipeline); + + return FALSE; +} + +static gboolean +_set_const_func (GQuark field_id, const GValue * value, GstStructure * consts) +{ + gst_structure_id_set_value (consts, field_id, value); + + return TRUE; +} + +static GstValidateExecuteActionReturn +_execute_define_vars (GstValidateScenario * scenario, + GstValidateAction * action) +{ + gst_structure_foreach (action->structure, + (GstStructureForeachFunc) _set_const_func, scenario->priv->vars); + + return GST_VALIDATE_EXECUTE_ACTION_OK; +} + +static GstValidateExecuteActionReturn +_set_timed_value (GQuark field_id, const GValue * gvalue, + GstStructure * structure) +{ + GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK; + gdouble value; + GstClockTime timestamp; + GstTimedValueControlSource *source = NULL; + GstControlBinding *binding; + GstValidateScenario *scenario; + GstValidateAction *action; + GstObject *obj = NULL; + GParamSpec *paramspec = NULL; + const gchar *field = g_quark_to_string (field_id); + const gchar *unused_fields[] = + { "binding-type", "source-type", "interpolation-mode", + "timestamp", "__scenario__", "__action__", "__res__", "repeat", + "playback-time", NULL + }; + + if (g_strv_contains (unused_fields, field)) + return TRUE; + + gst_structure_get (structure, "__scenario__", G_TYPE_POINTER, &scenario, + "__action__", G_TYPE_POINTER, &action, NULL); + + + if (G_VALUE_HOLDS_DOUBLE (gvalue)) + value = g_value_get_double (gvalue); + else if (G_VALUE_HOLDS_INT (gvalue)) + value = (gdouble) g_value_get_int (gvalue); + else { + GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR, + "Invalid value type for property '%s': %s", + field, G_VALUE_TYPE_NAME (gvalue)); + goto err; + } + + obj = _get_target_object_property (scenario, action, field, ¶mspec); + if (!obj || !paramspec) { + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + goto err; + } + + REPORT_UNLESS (gst_validate_action_get_clocktime (scenario, action, + "timestamp", ×tamp), err, + "Could get timestamp on %" GST_PTR_FORMAT, action->structure); + + binding = gst_object_get_control_binding (obj, paramspec->name); + if (!binding) { + guint mode; + GType source_type; + const gchar *interpolation_mode = + gst_structure_get_string (action->structure, "interpolation-mode"); + const gchar *source_type_name = + gst_structure_get_string (action->structure, "source-type"); + + if (source_type_name) { + source_type = g_type_from_name (source_type_name); + + REPORT_UNLESS (g_type_is_a (source_type, + GST_TYPE_TIMED_VALUE_CONTROL_SOURCE), err, + "Source type '%s' is not supported", source_type_name); + } else { + source_type = GST_TYPE_INTERPOLATION_CONTROL_SOURCE; + } + + source = g_object_new (source_type, NULL); + gst_object_ref_sink (source); + if (GST_IS_INTERPOLATION_CONTROL_SOURCE (source)) { + if (interpolation_mode) + REPORT_UNLESS (gst_validate_utils_enum_from_str + (GST_TYPE_INTERPOLATION_MODE, interpolation_mode, &mode), err, + "Could not convert interpolation-mode '%s'", interpolation_mode); + + else + mode = GST_INTERPOLATION_MODE_LINEAR; + + g_object_set (source, "mode", mode, NULL); + } + + if (!g_strcmp0 (gst_structure_get_string (action->structure, + "binding-type"), "direct-absolute")) { + binding = + gst_direct_control_binding_new_absolute (obj, paramspec->name, + GST_CONTROL_SOURCE (source)); + } else { + binding = + gst_direct_control_binding_new (obj, paramspec->name, + GST_CONTROL_SOURCE (source)); + } + + gst_object_add_control_binding (obj, binding); + } else { + g_object_get (binding, "control-source", &source, NULL); + } + + REPORT_UNLESS (GST_IS_TIMED_VALUE_CONTROL_SOURCE (source), err, + "Could not find timed value control source on %s", field); + + REPORT_UNLESS (gst_timed_value_control_source_set (source, timestamp, value), + err, "Could not set %s=%f at %" GST_TIME_FORMAT, field, value, + GST_TIME_ARGS (timestamp)); + + gst_object_unref (obj); + gst_structure_set (structure, "__res__", G_TYPE_INT, res, NULL); + + return TRUE; + +err: + gst_clear_object (&obj); + gst_structure_set (structure, "__res__", G_TYPE_INT, + GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED, NULL); + + return FALSE; +} + +static GstValidateExecuteActionReturn +_set_timed_value_property (GstValidateScenario * scenario, + GstValidateAction * action) +{ + GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_ERROR; + + gst_structure_set (action->structure, "__action__", G_TYPE_POINTER, + action, "__scenario__", G_TYPE_POINTER, scenario, NULL); + + gst_structure_foreach (action->structure, + (GstStructureForeachFunc) _set_timed_value, action->structure); + gst_structure_get_int (action->structure, "__res__", &res); + gst_structure_remove_fields (action->structure, "__action__", "__scenario__", + "__res__", NULL); + + return res; +} + + +static GstValidateExecuteActionReturn +_check_property (GstValidateScenario * scenario, GstValidateAction * action, + gpointer object, const gchar * propname, const GValue * expected_value) +{ + GValue cvalue = G_VALUE_INIT; + + g_value_init (&cvalue, G_VALUE_TYPE (expected_value)); + g_object_get_property (object, propname, &cvalue); + + if (gst_value_compare (&cvalue, expected_value) != GST_VALUE_EQUAL) { + gchar *expected = gst_value_serialize (expected_value), *observed = + gst_value_serialize (&cvalue); + + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "%" GST_PTR_FORMAT + "::%s expected value: '(%s)%s' different than observed: '(%s)%s'", + object, propname, G_VALUE_TYPE_NAME (&cvalue), expected, + G_VALUE_TYPE_NAME (expected_value), observed); + + g_free (expected); + g_free (observed); + + g_value_reset (&cvalue); + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + g_value_reset (&cvalue); + + return GST_VALIDATE_EXECUTE_ACTION_OK; + +} + +static GstValidateExecuteActionReturn +_set_or_check_properties (GQuark field_id, const GValue * value, + GstStructure * structure) +{ + GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK; + GstValidateScenario *scenario; + GstValidateAction *action; + GstObject *obj = NULL; + GParamSpec *paramspec = NULL; + const gchar *field = g_quark_to_string (field_id); + const gchar *unused_fields[] = { "__scenario__", "__action__", "__res__", + "playback-time", "repeat", NULL + }; + + if (g_strv_contains (unused_fields, field)) + return TRUE; + + gst_structure_get (structure, "__scenario__", G_TYPE_POINTER, &scenario, + "__action__", G_TYPE_POINTER, &action, NULL); + + obj = _get_target_object_property (scenario, action, field, ¶mspec); + if (!obj || !paramspec) { + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + goto done; + } + if (gst_structure_has_name (action->structure, "set-properties")) + res = gst_validate_object_set_property (GST_VALIDATE_REPORTER (scenario), + G_OBJECT (obj), paramspec->name, value, action->priv->optional); + else + res = _check_property (scenario, action, obj, paramspec->name, value); + +done: + gst_clear_object (&obj); + if (!gst_structure_has_field (structure, "__res__") + || res != GST_VALIDATE_EXECUTE_ACTION_OK) + gst_structure_set (structure, "__res__", G_TYPE_INT, res, NULL); + return TRUE; +} + +static GstValidateExecuteActionReturn +_execute_set_or_check_properties (GstValidateScenario * scenario, + GstValidateAction * action) +{ + GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_ERROR; + + gst_structure_set (action->structure, "__action__", G_TYPE_POINTER, + action, "__scenario__", G_TYPE_POINTER, scenario, NULL); + + gst_structure_foreach (action->structure, + (GstStructureForeachFunc) _set_or_check_properties, action->structure); + gst_structure_get_int (action->structure, "__res__", &res); + gst_structure_remove_fields (action->structure, "__action__", "__scenario__", + "__res__", NULL); + + return res; +} + +static GstValidateExecuteActionReturn +_execute_set_state (GstValidateScenario * scenario, GstValidateAction * action) +{ + guint state; + const gchar *str_state; + GstStateChangeReturn ret; + GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK; + + DECLARE_AND_GET_PIPELINE (scenario, action); + + g_return_val_if_fail ((str_state = + gst_structure_get_string (action->structure, "state")), FALSE); + + g_return_val_if_fail (gst_validate_utils_enum_from_str (GST_TYPE_STATE, + str_state, &state), FALSE); + + + scenario->priv->target_state = state; + scenario->priv->changing_state = TRUE; + scenario->priv->seeked_in_pause = FALSE; + + ret = gst_element_set_state (pipeline, state); + if (ret == GST_STATE_CHANGE_FAILURE) { + scenario->priv->changing_state = FALSE; + GST_VALIDATE_REPORT_ACTION (scenario, action, STATE_CHANGE_FAILURE, + "Failed to set state to %s", str_state); + + /* Nothing async on failure, action will be removed automatically */ + res = GST_VALIDATE_EXECUTE_ACTION_ERROR; + goto done; + } else if (ret == GST_STATE_CHANGE_ASYNC) { + + scenario->priv->needs_async_done = TRUE; + res = GST_VALIDATE_EXECUTE_ACTION_ASYNC; + + goto done; + } + + scenario->priv->changing_state = FALSE; + +done: + gst_object_unref (pipeline); + + return res; +} + +static GstValidateExecuteActionReturn +_execute_pause (GstValidateScenario * scenario, GstValidateAction * action) +{ + GstClockTime duration = 0; + GstValidateExecuteActionReturn ret; + + gst_validate_action_get_clocktime (scenario, action, "duration", &duration); + gst_structure_set (action->structure, "state", G_TYPE_STRING, "paused", NULL); + + GST_INFO_OBJECT (scenario, "Pausing for %" GST_TIME_FORMAT, + GST_TIME_ARGS (duration)); + + ret = _execute_set_state (scenario, action); + + if (ret != GST_VALIDATE_EXECUTE_ACTION_ERROR && duration) + g_timeout_add (GST_TIME_AS_MSECONDS (duration), + (GSourceFunc) _pause_action_restore_playing, scenario); + + return ret; +} + +static GstValidateExecuteActionReturn +_execute_play (GstValidateScenario * scenario, GstValidateAction * action) +{ + GST_DEBUG ("Playing back"); + + gst_structure_set (action->structure, "state", G_TYPE_STRING, + "playing", NULL); + + + return _execute_set_state (scenario, action); +} + +static gboolean +_action_sets_state (GstValidateAction * action) +{ + if (action == NULL) + return FALSE; + + if (g_strcmp0 (action->type, "set-state") == 0) + return TRUE; + + if (g_strcmp0 (action->type, "play") == 0) + return TRUE; + + if (g_strcmp0 (action->type, "pause") == 0) + return TRUE; + + return FALSE; + +} + +static void +gst_validate_scenario_check_dropped (GstValidateScenario * scenario) +{ + GstValidateScenarioPrivate *priv = scenario->priv; + + if (priv->max_dropped == -1 || priv->dropped == -1) + return; + + GST_DEBUG_OBJECT (scenario, "Number of dropped buffers: %d (max allowed: %d)", + priv->dropped, priv->max_dropped); + + if (priv->dropped > priv->max_dropped) { + GST_VALIDATE_REPORT (scenario, CONFIG_TOO_MANY_BUFFERS_DROPPED, + "Too many buffers have been dropped: %d (max allowed: %d)", + priv->dropped, priv->max_dropped); + } +} + +static GstValidateExecuteActionReturn +_execute_eos (GstValidateScenario * scenario, GstValidateAction * action) +{ + gboolean ret; + + DECLARE_AND_GET_PIPELINE (scenario, action); + + GST_DEBUG ("Sending EOS to pipeline at %" GST_TIME_FORMAT, + GST_TIME_ARGS (action->playback_time)); + + ret = gst_element_send_event (pipeline, gst_event_new_eos ()); + gst_object_unref (pipeline); + + return ret ? GST_VALIDATE_EXECUTE_ACTION_OK : + GST_VALIDATE_EXECUTE_ACTION_ERROR; +} + +static int +find_input_selector (GValue * velement, const gchar * type) +{ + GstElement *element = g_value_get_object (velement); + int result = !0; + + if (G_OBJECT_TYPE (element) == g_type_from_name ("GstInputSelector")) { + GstPad *srcpad = gst_element_get_static_pad (element, "src"); + + if (srcpad) { + GstCaps *caps = gst_pad_query_caps (srcpad, NULL); + + if (caps) { + const char *mime = + gst_structure_get_name (gst_caps_get_structure (caps, 0)); + gboolean found = FALSE; + + if (g_strcmp0 (type, "audio") == 0) + found = g_str_has_prefix (mime, "audio/"); + else if (g_strcmp0 (type, "video") == 0) + found = g_str_has_prefix (mime, "video/") + && !g_str_has_prefix (mime, "video/x-dvd-subpicture"); + else if (g_strcmp0 (type, "text") == 0) + found = g_str_has_prefix (mime, "text/") + || g_str_has_prefix (mime, "subtitle/") + || g_str_has_prefix (mime, "video/x-dvd-subpicture"); + + if (found) + result = 0; + } + + gst_caps_unref (caps); + gst_object_unref (srcpad); + } + } + return result; +} + +static GstElement * +find_input_selector_with_type (GstBin * bin, const gchar * type) +{ + GValue result = { 0, }; + GstElement *input_selector = NULL; + GstIterator *iterator = gst_bin_iterate_recurse (bin); + + if (gst_iterator_find_custom (iterator, + (GCompareFunc) find_input_selector, &result, (gpointer) type)) { + input_selector = g_value_get_object (&result); + } + gst_iterator_free (iterator); + + return input_selector; +} + +static GstPad * +find_nth_sink_pad (GstElement * element, int index) +{ + GstIterator *iterator; + gboolean done = FALSE; + GstPad *pad = NULL; + int dec_index = index; + GValue data = { 0, }; + + iterator = gst_element_iterate_sink_pads (element); + while (!done) { + switch (gst_iterator_next (iterator, &data)) { + case GST_ITERATOR_OK: + if (!dec_index--) { + done = TRUE; + pad = g_value_get_object (&data); + break; + } + g_value_reset (&data); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iterator); + dec_index = index; + break; + case GST_ITERATOR_ERROR: + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (iterator); + return pad; +} + +static int +find_sink_pad_index (GstElement * element, GstPad * pad) +{ + GstIterator *iterator; + gboolean done = FALSE; + int index = 0; + GValue data = { 0, }; + + iterator = gst_element_iterate_sink_pads (element); + while (!done) { + switch (gst_iterator_next (iterator, &data)) { + case GST_ITERATOR_OK: + if (pad == g_value_get_object (&data)) { + done = TRUE; + } else { + index++; + } + g_value_reset (&data); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iterator); + index = 0; + break; + case GST_ITERATOR_ERROR: + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (iterator); + return index; +} + +static GstPadProbeReturn +_check_select_pad_done (GstPad * pad, GstPadProbeInfo * info, + GstValidateAction * action) +{ + if (GST_BUFFER_FLAG_IS_SET (info->data, GST_BUFFER_FLAG_DISCONT)) { + gst_validate_action_set_done (action); + + return GST_PAD_PROBE_REMOVE; + } + + return GST_PAD_PROBE_OK; +} + +static GstValidateExecuteActionReturn +execute_switch_track_default (GstValidateScenario * scenario, + GstValidateAction * action) +{ + guint index; + gboolean relative = FALSE; + const gchar *type, *str_index; + GstElement *input_selector; + GstValidateExecuteActionReturn ret = GST_VALIDATE_EXECUTE_ACTION_ERROR; + + DECLARE_AND_GET_PIPELINE (scenario, action); + + if (!(type = gst_structure_get_string (action->structure, "type"))) + type = "audio"; + + /* First find an input selector that has the right type */ + input_selector = find_input_selector_with_type (GST_BIN (pipeline), type); + if (input_selector) { + GstState state, next; + GstPad *pad, *cpad, *srcpad; + + ret = GST_VALIDATE_EXECUTE_ACTION_OK; + str_index = gst_structure_get_string (action->structure, "index"); + + if (str_index == NULL) { + if (!gst_structure_get_uint (action->structure, "index", &index)) { + GST_WARNING ("No index given, defaulting to +1"); + index = 1; + relative = TRUE; + } + } else { + relative = strchr ("+-", str_index[0]) != NULL; + index = g_ascii_strtoll (str_index, NULL, 10); + } + + if (relative) { /* We are changing track relatively to current track */ + int npads; + + g_object_get (input_selector, "active-pad", &pad, "n-pads", &npads, NULL); + if (pad) { + int current_index = find_sink_pad_index (input_selector, pad); + + index = (current_index + index) % npads; + gst_object_unref (pad); + } + } + + pad = find_nth_sink_pad (input_selector, index); + g_object_get (input_selector, "active-pad", &cpad, NULL); + if (gst_element_get_state (pipeline, &state, &next, 0) && + state == GST_STATE_PLAYING && next == GST_STATE_VOID_PENDING) { + srcpad = gst_element_get_static_pad (input_selector, "src"); + + gst_pad_add_probe (srcpad, + GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BUFFER_LIST, + (GstPadProbeCallback) _check_select_pad_done, action, NULL); + ret = GST_VALIDATE_EXECUTE_ACTION_ASYNC; + gst_object_unref (srcpad); + } + + g_object_set (input_selector, "active-pad", pad, NULL); + gst_object_unref (pad); + gst_object_unref (cpad); + gst_object_unref (input_selector); + + goto done; + } + + /* No selector found -> Failed */ +done: + gst_object_unref (pipeline); + + return ret; +} + +static GstPadProbeReturn +_check_pad_event_selection_done (GstPad * pad, GstPadProbeInfo * info, + GstValidateAction * action) +{ + if (GST_EVENT_TYPE (info->data) == GST_EVENT_STREAM_START) { + gst_validate_action_set_done (action); + return GST_PAD_PROBE_REMOVE; + } + return GST_PAD_PROBE_OK; +} + +static GstValidateExecuteActionReturn +execute_switch_track_pb (GstValidateScenario * scenario, + GstValidateAction * action) +{ + gint index, n; + const gchar *type, *str_index; + + gint flags, current, tflag; + gchar *tmp, *current_txt; + + GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK; + gboolean relative = FALSE, disabling = FALSE; + + DECLARE_AND_GET_PIPELINE (scenario, action); + + if (!(type = gst_structure_get_string (action->structure, "type"))) + type = "audio"; + + tflag = gst_validate_utils_flags_from_str (g_type_from_name ("GstPlayFlags"), + type); + current_txt = g_strdup_printf ("current-%s", type); + + tmp = g_strdup_printf ("n-%s", type); + g_object_get (pipeline, "flags", &flags, tmp, &n, current_txt, ¤t, + NULL); + + /* Don't try to use -1 */ + if (current == -1) + current = 0; + + g_free (tmp); + + if (gst_structure_has_field (action->structure, "disable")) { + disabling = TRUE; + flags &= ~tflag; + index = -1; + } else if (!(str_index = + gst_structure_get_string (action->structure, "index"))) { + if (!gst_structure_get_int (action->structure, "index", &index)) { + GST_WARNING ("No index given, defaulting to +1"); + index = 1; + relative = TRUE; + } + } else { + relative = strchr ("+-", str_index[0]) != NULL; + index = g_ascii_strtoll (str_index, NULL, 10); + } + + if (relative) { /* We are changing track relatively to current track */ + if (n == 0) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Trying to execute a relative %s for %s track when there" + " is no track of this type available on current stream.", + action->type, type); + + res = GST_VALIDATE_EXECUTE_ACTION_ERROR; + goto done; + } + + index = (current + index) % n; + } + + if (!disabling) { + GstState state, next; + GstPad *oldpad, *newpad; + tmp = g_strdup_printf ("get-%s-pad", type); + g_signal_emit_by_name (G_OBJECT (pipeline), tmp, current, &oldpad); + g_signal_emit_by_name (G_OBJECT (pipeline), tmp, index, &newpad); + + gst_validate_printf (action, "Switching to track number: %i," + " (from %s:%s to %s:%s)\n", index, GST_DEBUG_PAD_NAME (oldpad), + GST_DEBUG_PAD_NAME (newpad)); + flags |= tflag; + g_free (tmp); + + if (gst_element_get_state (pipeline, &state, &next, 0) && + state == GST_STATE_PLAYING && next == GST_STATE_VOID_PENDING) { + GstPad *srcpad = NULL; + GstElement *combiner = NULL; + if (newpad == oldpad) { + srcpad = gst_pad_get_peer (oldpad); + } else if (newpad) { + combiner = GST_ELEMENT (gst_object_get_parent (GST_OBJECT (newpad))); + if (combiner) { + srcpad = gst_element_get_static_pad (combiner, "src"); + gst_object_unref (combiner); + } + } + + if (srcpad) { + gst_pad_add_probe (srcpad, + GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, + (GstPadProbeCallback) _check_pad_event_selection_done, action, + NULL); + gst_object_unref (srcpad); + + res = GST_VALIDATE_EXECUTE_ACTION_ASYNC; + } else + res = GST_VALIDATE_EXECUTE_ACTION_ERROR; + } + + if (oldpad) + gst_object_unref (oldpad); + gst_object_unref (newpad); + } else { + gst_validate_printf (action, "Disabling track type %s", type); + } + + g_object_set (pipeline, "flags", flags, current_txt, index, NULL); + g_free (current_txt); + +done: + gst_object_unref (pipeline); + return res; +} + +static GstStreamType +stream_type_from_string (const gchar * type) +{ + if (!g_strcmp0 (type, "video")) + return GST_STREAM_TYPE_VIDEO; + else if (!g_strcmp0 (type, "text")) + return GST_STREAM_TYPE_TEXT; + + /* default */ + return GST_STREAM_TYPE_AUDIO; +} + +/* Return a list of stream ID all the currently selected streams but the ones + * of type @type */ +static GList * +disable_stream (GstValidatePipelineMonitor * monitor, GstStreamType type) +{ + GList *streams = NULL, *l; + + for (l = monitor->streams_selected; l; l = g_list_next (l)) { + GstStream *s = l->data; + + if (gst_stream_get_stream_type (s) != type) { + streams = g_list_append (streams, (gpointer) s->stream_id); + } + } + + return streams; +} + +static GList * +switch_stream (GstValidatePipelineMonitor * monitor, GstValidateAction * action, + GstStreamType type, gint index, gboolean relative) +{ + guint nb_streams; + guint i, n = 0, current = 0; + GList *result = NULL, *l; + GstStream *streams[256], *s, *current_stream = NULL; + + /* Keep all streams which are not @type */ + for (l = monitor->streams_selected; l; l = g_list_next (l)) { + s = l->data; + + if (gst_stream_get_stream_type (s) != type) { + result = g_list_append (result, (gpointer) s->stream_id); + } else if (!current_stream) { + /* Assume the stream we want to switch from is the first one */ + current_stream = s; + } + } + + /* Calculate the number of @type streams */ + nb_streams = gst_stream_collection_get_size (monitor->stream_collection); + for (i = 0; i < nb_streams; i++) { + s = gst_stream_collection_get_stream (monitor->stream_collection, i); + + if (gst_stream_get_stream_type (s) == type) { + streams[n] = s; + + if (current_stream + && !g_strcmp0 (s->stream_id, current_stream->stream_id)) + current = n; + + n++; + } + } + + if (G_UNLIKELY (n == 0)) { + GST_ERROR ("No streams available of the required type"); + return result; + } + + if (relative) { /* We are changing track relatively to current track */ + index = (current + index) % n; + } else + index %= n; + + /* Add the new stream we want to switch to */ + s = streams[index]; + + gst_validate_printf (action, "Switching from stream %s to %s", + current_stream ? current_stream->stream_id : "", s->stream_id); + + return g_list_append (result, (gpointer) s->stream_id); +} + +static GstValidateExecuteActionReturn +execute_switch_track_pb3 (GstValidateScenario * scenario, + GstValidateAction * action) +{ + GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_ERROR; + GstValidateScenarioPrivate *priv = scenario->priv; + gint index; + GstStreamType stype; + const gchar *type, *str_index; + GList *new_streams = NULL; + GstValidatePipelineMonitor *monitor; + DECLARE_AND_GET_PIPELINE (scenario, action); + + monitor = (GstValidatePipelineMonitor *) (g_object_get_data ((GObject *) + pipeline, "validate-monitor")); + + if (!monitor->stream_collection) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "No stream collection message received on the bus, " + "can not switch track."); + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + goto done; + } + + if (!monitor->streams_selected) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "No streams selected message received on the bus"); + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + goto done; + } + + type = gst_structure_get_string (action->structure, "type"); + stype = stream_type_from_string (type); + + if (gst_structure_has_field (action->structure, "disable")) { + gst_validate_printf (action, "Disabling track type %s", type); + new_streams = disable_stream (monitor, stype); + } else { + gboolean relative = FALSE; + + if (!(str_index = gst_structure_get_string (action->structure, "index"))) { + if (!gst_structure_get_int (action->structure, "index", &index)) { + GST_WARNING ("No index given, defaulting to +1"); + index = 1; + relative = TRUE; + } + } else { + relative = strchr ("+-", str_index[0]) != NULL; + index = g_ascii_strtoll (str_index, NULL, 10); + } + + new_streams = switch_stream (monitor, action, stype, index, relative); + } + + gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (action), + ACTION_EXPECTED_STREAM_QUARK, g_list_copy (new_streams), + (GDestroyNotify) g_list_free); + + if (!gst_element_send_event (pipeline, + gst_event_new_select_streams (new_streams))) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, "select-streams event not handled"); + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + goto done; + } + + priv->pending_switch_track = action; + if (scenario->priv->target_state > GST_STATE_PAUSED) { + res = GST_VALIDATE_EXECUTE_ACTION_ASYNC; + } else { + gst_validate_action_ref (action); + res = GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING; + } + +done: + gst_object_unref (pipeline); + + return res; +} + +static GstValidateExecuteActionReturn +_execute_switch_track (GstValidateScenario * scenario, + GstValidateAction * action) +{ + GstValidatePipelineMonitor *monitor; + + DECLARE_AND_GET_PIPELINE (scenario, action); + + monitor = (GstValidatePipelineMonitor *) (g_object_get_data ((GObject *) + pipeline, "validate-monitor")); + gst_object_unref (pipeline); + + if (monitor->is_playbin) + return execute_switch_track_pb (scenario, action); + else if (monitor->is_playbin3) + return execute_switch_track_pb3 (scenario, action); + + return execute_switch_track_default (scenario, action); +} + +static GstValidateExecuteActionReturn +_execute_set_rank_or_disable_feature (GstValidateScenario * scenario, + GstValidateAction * action) +{ + GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK; + guint rank; + GList *features, *origlist; + GstPlugin *plugin; + GstPluginFeature *feature; + const gchar *name; + gboolean removing_feature = + gst_structure_has_name (action->structure, "remove-plugin-feature"); + GstRegistry *registry = gst_registry_get (); + + REPORT_UNLESS ( + (name = gst_structure_get_string (action->structure, "feature-name")) || + (name = gst_structure_get_string (action->structure, "name")), done, + "Could not find the name of the plugin/feature(s) to tweak"); + + if (removing_feature) + REPORT_UNLESS ( + (gst_structure_get_uint (action->structure, "rank", &rank)) || + (gst_structure_get_int (action->structure, "rank", (gint *) & rank)), + done, "Could not get rank to set on %s", name); + + feature = gst_registry_lookup_feature (registry, name); + if (feature) { + if (removing_feature) + gst_plugin_feature_set_rank (feature, rank); + else + gst_registry_remove_feature (registry, feature); + gst_object_unref (feature); + + goto done; + } + + REPORT_UNLESS ((plugin = gst_registry_find_plugin (registry, name)), + done, "Could not find %s", name); + + if (removing_feature) { + gst_registry_remove_plugin (registry, plugin); + goto done; + } + + origlist = features = + gst_registry_get_feature_list_by_plugin (registry, + gst_plugin_get_name (plugin)); + for (; features; features = features->next) + gst_plugin_feature_set_rank (features->data, rank); + gst_plugin_feature_list_free (origlist); + +done: + return res; +} + +static inline gboolean +_add_execute_actions_gsource (GstValidateScenario * scenario) +{ + GstValidateScenarioPrivate *priv = scenario->priv; + + SCENARIO_LOCK (scenario); + if (priv->execute_actions_source_id == 0 && priv->wait_id == 0 + && priv->signal_handler_id == 0 && priv->wait_message_action == NULL) { + if (!scenario->priv->action_execution_interval) + priv->execute_actions_source_id = + g_idle_add ((GSourceFunc) execute_next_action, scenario); + else + priv->execute_actions_source_id = + g_timeout_add (scenario->priv->action_execution_interval, + (GSourceFunc) execute_next_action, scenario); + SCENARIO_UNLOCK (scenario); + + GST_DEBUG_OBJECT (scenario, "Start checking position again"); + return TRUE; + } + SCENARIO_UNLOCK (scenario); + + GST_LOG_OBJECT (scenario, "No need to start a new gsource"); + return FALSE; +} + +static gboolean +_get_position (GstValidateScenario * scenario, + GstValidateAction * act, GstClockTime * position) +{ + gboolean has_pos = FALSE, has_dur = FALSE; + GstClockTime duration = -1; + + GstValidateScenarioPrivate *priv = scenario->priv; + GstElement *pipeline = gst_validate_scenario_get_pipeline (scenario); + + if (!pipeline) { + GST_ERROR_OBJECT (scenario, "No pipeline set anymore!"); + + return FALSE; + } + + has_pos = gst_element_query_position (pipeline, GST_FORMAT_TIME, + (gint64 *) position) + && GST_CLOCK_TIME_IS_VALID (*position); + has_dur = + gst_element_query_duration (pipeline, GST_FORMAT_TIME, + (gint64 *) & duration) + && GST_CLOCK_TIME_IS_VALID (duration); + + if (!has_pos && GST_STATE (pipeline) >= GST_STATE_PAUSED && + act && GST_CLOCK_TIME_IS_VALID (act->playback_time)) { + GST_INFO_OBJECT (scenario, "Unknown position: %" GST_TIME_FORMAT, + GST_TIME_ARGS (*position)); + + goto fail; + } + + if (has_pos && has_dur && !priv->got_eos) { + if (*position > duration) { + _add_execute_actions_gsource (scenario); + GST_VALIDATE_REPORT (scenario, + QUERY_POSITION_SUPERIOR_DURATION, + "Reported position %" GST_TIME_FORMAT " > reported duration %" + GST_TIME_FORMAT, GST_TIME_ARGS (*position), GST_TIME_ARGS (duration)); + + goto done; + } + } + +done: + gst_object_unref (pipeline); + return TRUE; + +fail: + gst_object_unref (pipeline); + return FALSE; +} + +static gboolean +_check_position (GstValidateScenario * scenario, GstValidateAction * act, + GstClockTime * position, gdouble * rate) +{ + GstQuery *query; + + GstClockTime start_with_tolerance, stop_with_tolerance; + GstValidateScenarioPrivate *priv = scenario->priv; + GstElement *pipeline; + + if (!_get_position (scenario, act, position)) + return FALSE; + + GST_DEBUG_OBJECT (scenario, "Current position: %" GST_TIME_FORMAT, + GST_TIME_ARGS (*position)); + + /* Check if playback is within seek segment */ + start_with_tolerance = (priv->segment_start < + priv->seek_pos_tol) ? 0 : priv->segment_start - priv->seek_pos_tol; + stop_with_tolerance = + GST_CLOCK_TIME_IS_VALID (priv->segment_stop) ? priv->segment_stop + + priv->seek_pos_tol : -1; + + if ((GST_CLOCK_TIME_IS_VALID (stop_with_tolerance) + && *position > stop_with_tolerance) + || (priv->seek_flags & GST_SEEK_FLAG_ACCURATE + && *position < start_with_tolerance + && priv->seek_format == GST_FORMAT_TIME)) { + + GST_VALIDATE_REPORT_ACTION (scenario, act, QUERY_POSITION_OUT_OF_SEGMENT, + "Current position %" GST_TIME_FORMAT " not in the expected range [%" + GST_TIME_FORMAT " -- %" GST_TIME_FORMAT, GST_TIME_ARGS (*position), + GST_TIME_ARGS (start_with_tolerance), + GST_TIME_ARGS (stop_with_tolerance)); + } + + pipeline = gst_validate_scenario_get_pipeline (scenario); + if (pipeline == NULL) { + GST_INFO_OBJECT (scenario, "No pipeline set anymore"); + + return TRUE; + } + + query = gst_query_new_segment (GST_FORMAT_DEFAULT); + if (gst_element_query (GST_ELEMENT (pipeline), query)) + gst_query_parse_segment (query, rate, NULL, NULL, NULL); + gst_query_unref (query); + gst_object_unref (pipeline); + + if (priv->seeked_in_pause && priv->seek_flags & GST_SEEK_FLAG_ACCURATE && + priv->seek_format == GST_FORMAT_TIME) { + if (*rate > 0 + && (GstClockTime) ABS (GST_CLOCK_DIFF (*position, + priv->segment_start)) > priv->seek_pos_tol) { + priv->seeked_in_pause = FALSE; + GST_VALIDATE_REPORT_ACTION (scenario, act, + EVENT_SEEK_RESULT_POSITION_WRONG, + "Reported position after accurate seek in PAUSED state should be exactly" + " what the user asked for. Position %" GST_TIME_FORMAT + " is not not the expected one: %" GST_TIME_FORMAT, + GST_TIME_ARGS (*position), GST_TIME_ARGS (priv->segment_start)); + } + } + + return TRUE; +} + +static gboolean +_check_message_type (GstValidateScenario * scenario, GstValidateAction * act, + GstMessage * message) +{ + return act && message + && !g_strcmp0 (gst_structure_get_string (act->structure, "on-message"), + gst_message_type_get_name (GST_MESSAGE_TYPE (message))); +} + +static gboolean +_should_execute_action (GstValidateScenario * scenario, GstValidateAction * act, + GstClockTime position, gdouble rate) +{ + GstElement *pipeline = NULL; + + if (!act) { + GST_DEBUG_OBJECT (scenario, "No action to execute"); + + goto no; + } + + pipeline = gst_validate_scenario_get_pipeline (scenario); + if (pipeline == NULL) { + + if (!(GST_VALIDATE_ACTION_GET_TYPE (act)->flags & + GST_VALIDATE_ACTION_TYPE_DOESNT_NEED_PIPELINE)) { + GST_VALIDATE_REPORT_ACTION (scenario, act, + SCENARIO_ACTION_EXECUTION_ERROR, + "Trying to execute an %s action after the pipeline has been destroyed" + " but the type has not been marked as " + "GST_VALIDATE_ACTION_TYPE_DOESNT_NEED_PIPELINE", act->type); + + return FALSE; + } else if (GST_CLOCK_TIME_IS_VALID (act->playback_time)) { + GST_VALIDATE_REPORT_ACTION (scenario, act, + SCENARIO_ACTION_EXECUTION_ERROR, + "Trying to execute action %s with playback time %" GST_TIME_FORMAT + " after the pipeline has been destroyed. It is impossible" + " to execute an action with a playback time specified" + " after the pipeline has been destroyed", act->type, + GST_TIME_ARGS (act->playback_time)); + + goto no; + } + + GST_DEBUG_OBJECT (scenario, "No pipeline, go and execute action!"); + + goto yes; + } else if (scenario->priv->got_eos) { + GST_DEBUG_OBJECT (scenario, "Just got EOS go and execute next action!"); + scenario->priv->got_eos = FALSE; + } else if (GST_STATE (pipeline) < GST_STATE_PAUSED) { + GST_DEBUG_OBJECT (scenario, "Pipeline not even in paused, " + "just executing actions"); + + goto yes; + } else if (act->playback_time == GST_CLOCK_TIME_NONE) { + GST_DEBUG_OBJECT (scenario, "No timing info, executing action"); + + goto yes; + } else if ((rate > 0 && (GstClockTime) position < act->playback_time)) { + GST_DEBUG_OBJECT (scenario, "positive rate and position %" GST_TIME_FORMAT + " < playback_time %" GST_TIME_FORMAT, GST_TIME_ARGS (position), + GST_TIME_ARGS (act->playback_time)); + + goto no; + } else if (rate < 0 && (GstClockTime) position > act->playback_time) { + GST_DEBUG_OBJECT (scenario, "negative rate and position %" GST_TIME_FORMAT + " < playback_time %" GST_TIME_FORMAT, GST_TIME_ARGS (position), + GST_TIME_ARGS (act->playback_time)); + + goto no; + } + +yes: + gst_object_unref (pipeline); + return TRUE; + +no: + gst_clear_object (&pipeline); + return FALSE; +} + +static gboolean +_set_action_playback_time (GstValidateScenario * scenario, + GstValidateAction * action) +{ + if (!gst_validate_action_get_clocktime (scenario, action, + "playback-time", &action->playback_time)) { + gst_validate_error_structure (action, + "Could not parse playback-time in %" GST_PTR_FORMAT, action->structure); + + return FALSE; + } + + gst_structure_set (action->structure, "playback-time", GST_TYPE_CLOCK_TIME, + action->playback_time, NULL); + + return TRUE; +} + +static gboolean +gst_validate_parse_next_action_playback_time (GstValidateScenario * self) +{ + GstValidateAction *action; + GstValidateScenarioPrivate *priv = self->priv; + + if (!priv->actions) + return TRUE; + + action = (GstValidateAction *) priv->actions->data; + if (!action->priv->needs_playback_parsing) + return TRUE; + + if (!_set_action_playback_time (self, action)) { + GST_ERROR_OBJECT (self, "Could not set playback_time!"); + + return FALSE; + } + action->priv->needs_playback_parsing = FALSE; + + return TRUE; +} + +static gboolean +_foreach_find_iterator (GQuark field_id, GValue * value, + GstValidateAction * action) +{ + const gchar *field = g_quark_to_string (field_id); + + if (!g_strcmp0 (field, "actions")) + return TRUE; + + if (!GST_VALUE_HOLDS_INT_RANGE (value) && !GST_VALUE_HOLDS_ARRAY (value)) { + gst_validate_error_structure (action, + "Unsupported iterator type `%s` for %s" + ". Only ranges (`[(int)start, (int)stop, [(int)step]]`) and arrays " + " (``) are supported", field, G_VALUE_TYPE_NAME (value)); + return TRUE; + } + + if (GST_VALIDATE_ACTION_RANGE_NAME (action)) { + gst_validate_error_structure (action, "Wrong iterator syntax, " + " only one iterator field is supported."); + return FALSE; + } + + GST_VALIDATE_ACTION_RANGE_NAME (action) = field; + return TRUE; +} + + +GstValidateExecuteActionReturn +gst_validate_execute_action (GstValidateActionType * action_type, + GstValidateAction * action) +{ + GstValidateExecuteActionReturn res; + GstValidateScenario *scenario; + + g_return_val_if_fail (g_strcmp0 (action_type->name, action->type) == 0, + GST_VALIDATE_EXECUTE_ACTION_ERROR); + + scenario = gst_validate_action_get_scenario (action); + g_assert (scenario); + + action->priv->context = g_main_context_ref (scenario->priv->context); + if (action_type->prepare) { + res = action_type->prepare (action); + if (res == GST_VALIDATE_EXECUTE_ACTION_DONE) { + gst_validate_print_action (action, NULL); + return GST_VALIDATE_EXECUTE_ACTION_OK; + } + + if (res != GST_VALIDATE_EXECUTE_ACTION_OK) { + GST_ERROR_OBJECT (scenario, "Action %" GST_PTR_FORMAT + " could not be prepared", action->structure); + + gst_object_unref (scenario); + return res; + } + } + + gst_validate_print_action (action, NULL); + + action->priv->execution_time = gst_util_get_timestamp (); + action->priv->state = GST_VALIDATE_EXECUTE_ACTION_IN_PROGRESS; + action_type->priv->n_calls++; + res = action_type->execute (scenario, action); + gst_object_unref (scenario); + + return res; +} + +/* scenario can be NULL **only** if the action is a CONFIG action and + * add_to_lists is FALSE */ +static GstValidateExecuteActionReturn +_fill_action (GstValidateScenario * scenario, GstValidateAction * action, + GstStructure * structure, gboolean add_to_lists) +{ + gdouble playback_time; + gboolean is_config = FALSE; + GstValidateActionType *action_type; + GstValidateScenarioPrivate *priv = scenario ? scenario->priv : NULL; + GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_NONE; + gboolean optional, needs_parsing = FALSE; + + action->type = gst_structure_get_name (structure); + action_type = _find_action_type (action->type); + + if (!action_type) { + GST_ERROR_OBJECT (scenario, "Action type %s no found", + gst_structure_get_name (structure)); + + return GST_VALIDATE_EXECUTE_ACTION_ERROR; + } + + if (gst_structure_get_double (structure, "playback-time", &playback_time) || + gst_structure_get_double (structure, "playback_time", &playback_time)) { + action->playback_time = playback_time * GST_SECOND; + } else if (gst_structure_has_field_typed (structure, "playback-time", + G_TYPE_STRING) + || gst_structure_has_field_typed (structure, "playback_time", + G_TYPE_STRING)) { + + if (add_to_lists && priv) { + action->priv->needs_playback_parsing = TRUE; + needs_parsing = TRUE; + } + } else + GST_INFO_OBJECT (scenario, + "No playback time for action %" GST_PTR_FORMAT, structure); + + if (!gst_validate_utils_get_clocktime (structure, + "timeout", &action->priv->timeout)) { + GST_INFO_OBJECT (scenario, + "No timeout time for action %" GST_PTR_FORMAT, structure); + } + + action->structure = gst_structure_copy (structure); + + if (!(action->name = gst_structure_get_string (action->structure, "name"))) + action->name = ""; + + if (!action->priv->main_structure) + action->priv->main_structure = gst_structure_copy (structure); + + if (gst_structure_get_boolean (structure, "optional", &optional)) { + if ((action_type->flags & GST_VALIDATE_ACTION_TYPE_CAN_BE_OPTIONAL) == 0) { + GST_ERROR_OBJECT (scenario, "Action type %s can't be optional", + gst_structure_get_name (structure)); + return GST_VALIDATE_EXECUTE_ACTION_ERROR; + } + action->priv->optional = optional; + } + + if (IS_CONFIG_ACTION_TYPE (action_type->flags) || + (gst_structure_get_boolean (action->structure, "as-config", + &is_config) && is_config == TRUE)) { + + action_type->priv->n_calls++; + res = action_type->execute (scenario, action); + gst_validate_print_action (action, NULL); + + return res; + } + + if (!add_to_lists) + return res; + + if (priv != NULL) { + GstValidateActionType *type = _find_action_type (action->type); + gboolean can_execute_on_addition = + type->flags & GST_VALIDATE_ACTION_TYPE_CAN_EXECUTE_ON_ADDITION + && !GST_CLOCK_TIME_IS_VALID (action->playback_time) + && !gst_structure_has_field (action->structure, "on-message"); + + if (needs_parsing) + can_execute_on_addition = FALSE; + + if (can_execute_on_addition) { + GList *tmp; + + for (tmp = priv->actions; tmp; tmp = tmp->next) { + GstValidateAction *act = (GstValidateAction *) tmp->data; + if (GST_CLOCK_TIME_IS_VALID (act->playback_time)) { + can_execute_on_addition = FALSE; + break; + } + } + + } + + if (can_execute_on_addition) { + SCENARIO_LOCK (scenario); + priv->on_addition_actions = g_list_append (priv->on_addition_actions, + action); + SCENARIO_UNLOCK (scenario); + + } else { + priv->actions = g_list_append (priv->actions, action); + } + } + + return res; +} + +static gboolean +gst_validate_scenario_execute_next_or_restart_looping (GstValidateScenario * + scenario) +{ + /* Recurse to the next action if it is possible + * to execute right away */ + if (!scenario->priv->execute_on_idle) { + GST_DEBUG_OBJECT (scenario, "linking next action execution"); + + return execute_next_action (scenario); + } else { + _add_execute_actions_gsource (scenario); + GST_DEBUG_OBJECT (scenario, "Executing only on idle, waiting for" + " next dispatch"); + } + return G_SOURCE_CONTINUE; +} + +/* This is the main action execution function + * it checks whether it is time to run the next action + * and if it is the case executes it. + * + * If the 'execute-on-idle' property is not TRUE, + * the function will recurse while the actions are run + * synchronously + */ +static gboolean +execute_next_action_full (GstValidateScenario * scenario, GstMessage * message) +{ + gdouble rate = 1.0; + GstClockTime position = -1; + GstValidateAction *act = NULL; + GstValidateActionType *type; + + GstValidateScenarioPrivate *priv = scenario->priv; + + if (priv->buffering) { + GST_DEBUG_OBJECT (scenario, "Buffering not executing any action"); + + return G_SOURCE_CONTINUE; + } + + if (priv->changing_state || priv->needs_async_done) { + GST_DEBUG_OBJECT (scenario, "Changing state, not executing any action"); + return G_SOURCE_CONTINUE; + } + + if (scenario->priv->actions) + act = scenario->priv->actions->data; + + if (!act) { + _check_scenario_is_done (scenario); + return G_SOURCE_CONTINUE; + } + + switch (act->priv->state) { + case GST_VALIDATE_EXECUTE_ACTION_NONE: + case GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING: + break; + case GST_VALIDATE_EXECUTE_ACTION_IN_PROGRESS: + return G_SOURCE_CONTINUE; + case GST_VALIDATE_EXECUTE_ACTION_ASYNC: + if (GST_CLOCK_TIME_IS_VALID (act->priv->timeout)) { + GstClockTime etime = + gst_util_get_timestamp () - act->priv->execution_time; + + if (etime > act->priv->timeout) { + gchar *str = gst_structure_to_string (act->structure); + + GST_VALIDATE_REPORT_ACTION (scenario, act, + SCENARIO_ACTION_EXECUTION_ERROR, + "Action %s timed out after: %" GST_TIME_FORMAT, str, + GST_TIME_ARGS (etime)); + + g_free (str); + } + } + GST_LOG_OBJECT (scenario, "Action %" GST_PTR_FORMAT " still running", + act->structure); + + return G_SOURCE_CONTINUE; + default: + GST_ERROR ("State is %d", act->priv->state); + g_assert_not_reached (); + } + + if (message) { + if (!_check_message_type (scenario, act, message)) + return G_SOURCE_CONTINUE; + } else if ((act && gst_structure_get_string (act->structure, "on-message") && + !GST_CLOCK_TIME_IS_VALID (act->playback_time)) || + (!_check_position (scenario, act, &position, &rate))) { + return G_SOURCE_CONTINUE; + } + + if (!_should_execute_action (scenario, act, position, rate)) { + _add_execute_actions_gsource (scenario); + + return G_SOURCE_CONTINUE; + } + + type = _find_action_type (act->type); + + GST_DEBUG_OBJECT (scenario, "Executing %" GST_PTR_FORMAT + " at %" GST_TIME_FORMAT, act->structure, GST_TIME_ARGS (position)); + priv->seeked_in_pause = FALSE; + + if (message) + gst_structure_remove_field (act->structure, "playback-time"); + else + gst_structure_remove_field (act->structure, "on-message"); + + act->priv->state = gst_validate_execute_action (type, act); + switch (act->priv->state) { + case GST_VALIDATE_EXECUTE_ACTION_ASYNC: + GST_DEBUG_OBJECT (scenario, "Remove source, waiting for action" + " to be done."); + + SCENARIO_LOCK (scenario); + priv->execute_actions_source_id = 0; + SCENARIO_UNLOCK (scenario); + + return G_SOURCE_CONTINUE; + case GST_VALIDATE_EXECUTE_ACTION_IN_PROGRESS: + return G_SOURCE_CONTINUE; + case GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING: + SCENARIO_LOCK (scenario); + priv->non_blocking_running_actions = + g_list_append (priv->non_blocking_running_actions, act); + priv->actions = g_list_remove (priv->actions, act); + SCENARIO_UNLOCK (scenario); + return gst_validate_scenario_execute_next_or_restart_looping (scenario); + default: + gst_validate_action_set_done (act); + return G_SOURCE_CONTINUE; + } +} + +static gboolean +execute_next_action (GstValidateScenario * scenario) +{ + return execute_next_action_full (scenario, NULL); +} + +static gboolean +stop_waiting (GstValidateAction * action) +{ + GstValidateScenario *scenario = gst_validate_action_get_scenario (action); + + SCENARIO_LOCK (scenario); + scenario->priv->wait_id = 0; + SCENARIO_UNLOCK (scenario); + + gst_validate_action_set_done (action); + _add_execute_actions_gsource (scenario); + gst_object_unref (scenario); + + + return G_SOURCE_REMOVE; +} + +static void +stop_waiting_signal (GstStructure * data) +{ + guint sigid = 0; + GstElement *target; + GstValidateAction *action; + GstValidateScenario *scenario; + + gst_structure_get (data, "target", G_TYPE_POINTER, &target, + "action", GST_TYPE_VALIDATE_ACTION, &action, "sigid", G_TYPE_UINT, &sigid, + NULL); + gst_structure_free (data); + + scenario = gst_validate_action_get_scenario (action); + + g_assert (scenario); + SCENARIO_LOCK (scenario); + g_signal_handler_disconnect (target, + sigid ? sigid : scenario->priv->signal_handler_id); + if (!sigid) + scenario->priv->signal_handler_id = 0; + SCENARIO_UNLOCK (scenario); + + gst_validate_action_set_done (action); + gst_validate_action_unref (action); + _add_execute_actions_gsource (scenario); + gst_object_unref (scenario); + gst_object_unref (target); +} + +static GstValidateExecuteActionReturn +_execute_timed_wait (GstValidateScenario * scenario, GstValidateAction * action) +{ + GstValidateScenarioPrivate *priv = scenario->priv; + GstClockTime duration; + + gdouble wait_multiplier = 1; + const gchar *str_wait_multiplier = + g_getenv ("GST_VALIDATE_SCENARIO_WAIT_MULTIPLIER"); + + if (str_wait_multiplier) { + errno = 0; + wait_multiplier = g_ascii_strtod (str_wait_multiplier, NULL); + + if (errno) { + GST_ERROR ("Could not use the WAIT MULTIPLIER"); + + wait_multiplier = 1; + } + + if (wait_multiplier == 0) { + GST_INFO_OBJECT (scenario, "I have been told not to wait..."); + return GST_VALIDATE_EXECUTE_ACTION_OK; + } + } + + if (!gst_validate_action_get_clocktime (scenario, action, + "duration", &duration)) { + GST_DEBUG_OBJECT (scenario, "Duration could not be parsed"); + + return GST_VALIDATE_EXECUTE_ACTION_ERROR; + } + + duration *= wait_multiplier; + + SCENARIO_LOCK (scenario); + if (priv->execute_actions_source_id) { + g_source_remove (priv->execute_actions_source_id); + priv->execute_actions_source_id = 0; + } + SCENARIO_UNLOCK (scenario); + + SCENARIO_LOCK (scenario); + priv->wait_id = g_timeout_add (duration / G_USEC_PER_SEC, + (GSourceFunc) stop_waiting, action); + SCENARIO_UNLOCK (scenario); + + return GST_VALIDATE_EXECUTE_ACTION_ASYNC; +} + +static GstValidateExecuteActionReturn +_execute_wait_for_signal (GstValidateScenario * scenario, + GstValidateAction * action) +{ + gboolean non_blocking; + GstValidateScenarioPrivate *priv = scenario->priv; + const gchar *signal_name = gst_structure_get_string + (action->structure, "signal-name"); + GstElement *target; + GstStructure *data; + GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK; + DECLARE_AND_GET_PIPELINE (scenario, action); + + REPORT_UNLESS (signal_name, err, "No signal-name given for wait action"); + REPORT_UNLESS ((target = _get_target_element (scenario, action)), err, + "Could not find target element."); + + gst_validate_printf (action, "Waiting for '%s' signal\n", signal_name); + + if (priv->execute_actions_source_id) { + g_source_remove (priv->execute_actions_source_id); + priv->execute_actions_source_id = 0; + } + + data = + gst_structure_new ("a", "action", GST_TYPE_VALIDATE_ACTION, action, + "target", G_TYPE_POINTER, target, NULL); + SCENARIO_LOCK (scenario); + priv->signal_handler_id = g_signal_connect_swapped (target, signal_name, + (GCallback) stop_waiting_signal, data); + + non_blocking = + gst_structure_get_boolean (action->structure, "non-blocking", + &non_blocking); + if (non_blocking) { + gst_structure_set (data, "sigid", G_TYPE_UINT, priv->signal_handler_id, + NULL); + priv->signal_handler_id = 0; + } + SCENARIO_UNLOCK (scenario); + + gst_object_unref (pipeline); + + + return non_blocking ? GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING : + GST_VALIDATE_EXECUTE_ACTION_ASYNC; + +err: + gst_object_unref (pipeline); + return res; +} + +static gboolean +_execute_wait_for_message (GstValidateScenario * scenario, + GstValidateAction * action) +{ + GstValidateScenarioPrivate *priv = scenario->priv; + const gchar *message_type = gst_structure_get_string + (action->structure, "message-type"); + DECLARE_AND_GET_PIPELINE (scenario, action); + + gst_validate_printf (action, "Waiting for '%s' message\n", message_type); + + if (priv->execute_actions_source_id) { + g_source_remove (priv->execute_actions_source_id); + priv->execute_actions_source_id = 0; + } + + g_assert (!priv->wait_message_action); + priv->wait_message_action = gst_validate_action_ref (action); + gst_object_unref (pipeline); + + return GST_VALIDATE_EXECUTE_ACTION_ASYNC; +} + +static GstValidateExecuteActionReturn +_execute_wait (GstValidateScenario * scenario, GstValidateAction * action) +{ + gboolean on_clock = FALSE; + + gst_structure_get_boolean (action->structure, "on-clock", &on_clock); + if (gst_structure_has_field (action->structure, "signal-name")) { + return _execute_wait_for_signal (scenario, action); + } else if (gst_structure_has_field (action->structure, "message-type")) { + return _execute_wait_for_message (scenario, action); + } else if (on_clock) { + gst_test_clock_wait_for_next_pending_id (scenario->priv->clock, NULL); + + return GST_VALIDATE_EXECUTE_ACTION_OK; + } else { + return _execute_timed_wait (scenario, action); + } + + return FALSE; +} + +static gboolean +_execute_dot_pipeline (GstValidateScenario * scenario, + GstValidateAction * action) +{ + gchar *dotname; + gint details = GST_DEBUG_GRAPH_SHOW_ALL; + const gchar *name = gst_structure_get_string (action->structure, "name"); + DECLARE_AND_GET_PIPELINE (scenario, action); + + gst_structure_get_int (action->structure, "details", &details); + if (name) + dotname = g_strdup_printf ("validate.action.%s", name); + else + dotname = g_strdup ("validate.action.unnamed"); + + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline), details, dotname); + + g_free (dotname); + gst_object_unref (pipeline); + + return TRUE; +} + +static GstElement * +_get_target_element (GstValidateScenario * scenario, GstValidateAction * action) +{ + const gchar *name; + GstElement *target; + GstElement *pipeline = gst_validate_scenario_get_pipeline (scenario); + + if (!pipeline) { + GST_ERROR_OBJECT (scenario, "No pipeline set anymore!"); + + return NULL; + } + + name = gst_structure_get_string (action->structure, "target-element-name"); + if (name == NULL) { + gst_object_unref (pipeline); + + return NULL; + } + + if (g_strcmp0 (GST_OBJECT_NAME (pipeline), name) == 0) { + target = gst_object_ref (pipeline); + } else { + target = gst_bin_get_by_name (GST_BIN (pipeline), name); + } + + if (target == NULL) + GST_ERROR ("Target element with given name (%s) not found", name); + gst_object_unref (pipeline); + + return target; +} + +/* _get_target_elements_by_klass_or_factory_name: + * @scenario: a #GstValidateScenario + * @action: a #GstValidateAction + * + * Returns all the elements in the pipeline whose GST_ELEMENT_METADATA_KLASS + * matches the 'target-element-klass' of @action and the factory name matches + * the 'target-element-factory-name'. + * + * Returns: (transfer full) (element-type GstElement): a list of #GstElement + */ +static GList * +_get_target_elements_by_klass_or_factory_name (GstValidateScenario * scenario, + GstValidateAction * action) +{ + GList *result = NULL; + GstIterator *it; + const gchar *klass, *fname; + GValue v = G_VALUE_INIT, param = G_VALUE_INIT; + gboolean done = FALSE; + GstElement *pipeline = gst_validate_scenario_get_pipeline (scenario); + + if (!pipeline) { + GST_ERROR_OBJECT (scenario, "No pipeline set anymore!"); + + return NULL; + } + + klass = gst_structure_get_string (action->structure, "target-element-klass"); + fname = + gst_structure_get_string (action->structure, + "target-element-factory-name"); + if (!klass && !fname) { + gst_object_unref (pipeline); + + return NULL; + } + + if (klass && gst_validate_element_has_klass (pipeline, klass)) + result = g_list_prepend (result, gst_object_ref (pipeline)); + + if (fname && gst_element_get_factory (pipeline) + && !g_strcmp0 (GST_OBJECT_NAME (gst_element_get_factory (pipeline)), + fname)) + result = g_list_prepend (result, gst_object_ref (pipeline)); + + it = gst_bin_iterate_recurse (GST_BIN (pipeline)); + + g_value_init (¶m, G_TYPE_STRING); + g_value_set_string (¶m, klass); + + while (!done) { + switch (gst_iterator_next (it, &v)) { + case GST_ITERATOR_OK:{ + GstElement *child = g_value_get_object (&v); + + if (g_list_find (result, child)) + goto next; + + if (klass && gst_validate_element_has_klass (child, klass)) { + result = g_list_prepend (result, gst_object_ref (child)); + goto next; + } + + if (fname && gst_element_get_factory (child) + && !g_strcmp0 (GST_OBJECT_NAME (gst_element_get_factory (child)), + fname)) + result = g_list_prepend (result, gst_object_ref (child)); + next: + g_value_reset (&v); + } + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (it); + break; + case GST_ITERATOR_ERROR: + case GST_ITERATOR_DONE: + done = TRUE; + } + } + + g_value_reset (&v); + g_value_reset (¶m); + gst_iterator_free (it); + gst_object_unref (pipeline); + + return result; +} + +static GList * +_find_elements_defined_in_action (GstValidateScenario * scenario, + GstValidateAction * action) +{ + GstElement *target; + GList *targets = NULL; + + /* set-property can be applied on either: + * - a single element having target-element-name as name + * - all the elements having target-element-klass as klass + */ + if (gst_structure_get_string (action->structure, "target-element-name")) { + target = _get_target_element (scenario, action); + if (target == NULL) + return FALSE; + + targets = g_list_append (targets, target); + } else if (gst_structure_get_string (action->structure, + "target-element-klass") || + gst_structure_get_string (action->structure, + "target-element-factory-name")) { + targets = _get_target_elements_by_klass_or_factory_name (scenario, action); + } + + return targets; +} + +static GstValidateExecuteActionReturn +_execute_check_action_type_calls (GstValidateScenario * scenario, + GstValidateAction * action) +{ + const gchar *type; + GstValidateActionType *t; + GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK; + gint n; + + REPORT_UNLESS (gst_structure_get_int (action->structure, "n", &n), + done, "No `n`!"); + REPORT_UNLESS ((type = gst_structure_get_string (action->structure, "type")), + done, "No `type`!"); + REPORT_UNLESS ((t = + _find_action_type (type)), done, "Can't find `%s`!", type); + REPORT_UNLESS (t->priv->n_calls == n, done, + "%s called %d times instead of expected %d", type, t->priv->n_calls, n); + + +done: + return res; +} + +static GstValidateExecuteActionReturn +_execute_check_subaction_level (GstValidateScenario * scenario, + GstValidateAction * action) +{ + GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK; + gint n; + + REPORT_UNLESS (gst_structure_get_int (action->structure, "level", &n), + done, "No `n`!"); + REPORT_UNLESS (gst_validate_action_get_level (action) == n, done, + "Expected subaction level %d, got %d", n, + gst_validate_action_get_level (action)); + + +done: + return res; +} + +static gboolean +set_env_var (GQuark field_id, GValue * value, + GSubprocessLauncher * subproc_launcher) +{ + g_subprocess_launcher_setenv (subproc_launcher, g_quark_to_string (field_id), + g_value_get_string (value), TRUE); + + return TRUE; +} + +static GstValidateExecuteActionReturn +_run_command (GstValidateScenario * scenario, GstValidateAction * action) +{ + gchar **argv = NULL, *_stderr; + GError *error = NULL; + const GValue *env = NULL; + GSubprocess *subproc = NULL; + GSubprocessLauncher *subproc_launcher = NULL; + GstValidateExecuteActionReturn res = + GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + + REPORT_UNLESS ((argv = gst_validate_utils_get_strv (action->structure, + "argv")), done, + "Couldn't find `argv` as array of strings in %" GST_PTR_FORMAT, + action->structure); + + subproc_launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDERR_PIPE); + g_subprocess_launcher_unsetenv (subproc_launcher, "GST_VALIDATE_SCENARIO"); + g_subprocess_launcher_unsetenv (subproc_launcher, "GST_VALIDATE_CONFIG"); + + env = gst_structure_get_value (action->structure, "env"); + REPORT_UNLESS (!env || GST_VALUE_HOLDS_STRUCTURE (env), done, + "The `env` parameter should be a GstStructure, got %s", + G_VALUE_TYPE_NAME (env)); + if (env) { + gst_structure_foreach (gst_value_get_structure (env), + (GstStructureForeachFunc) set_env_var, subproc_launcher); + } + + REPORT_UNLESS ( + (subproc = + g_subprocess_launcher_spawnv (subproc_launcher, + (const gchar * const *) argv, &error)), done, + "Couldn't start subprocess: %s", error->message); + + REPORT_UNLESS (g_subprocess_communicate_utf8 (subproc, NULL, NULL, NULL, + &_stderr, &error), done, "Failed to run check: %s", error->message); + + REPORT_UNLESS (g_subprocess_get_exit_status (subproc) == 0, + done, "Sub command failed. Stderr: %s", _stderr); + + res = GST_VALIDATE_EXECUTE_ACTION_OK; + +done: + if (argv) + g_strfreev (argv); + g_clear_object (&subproc_launcher); + g_clear_object (&subproc); + + return res; +} + +static GstValidateExecuteActionReturn +_execute_check_position (GstValidateScenario * scenario, + GstValidateAction * action) +{ + GstClockTime expected_pos, pos; + + if (!gst_validate_action_get_clocktime (scenario, action, + "expected-position", &expected_pos)) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Could not retrieve expected position in: %" GST_PTR_FORMAT, + action->structure); + + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + + if (!_get_position (scenario, NULL, &pos)) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, "Could not get pipeline position"); + + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + + if (pos != expected_pos) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Pipeline position doesn't match expectations" + " got %" GST_TIME_FORMAT " instead of %" GST_TIME_FORMAT, + GST_TIME_ARGS (pos), GST_TIME_ARGS (expected_pos)); + + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + + return GST_VALIDATE_EXECUTE_ACTION_OK; + +} + +static GstValidateExecuteActionReturn +_execute_set_or_check_property (GstValidateScenario * scenario, + GstValidateAction * action) +{ + GList *targets, *l; + const gchar *property; + const GValue *property_value; + gboolean ret = GST_VALIDATE_EXECUTE_ACTION_OK; + gboolean check = gst_structure_has_name (action->structure, "check-property"); + + targets = _find_elements_defined_in_action (scenario, action); + if (!targets) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "No element found for action: %" GST_PTR_FORMAT, action->structure); + + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + + property = gst_structure_get_string (action->structure, "property-name"); + property_value = gst_structure_get_value (action->structure, + "property-value"); + + for (l = targets; l != NULL; l = g_list_next (l)) { + if (!check) { + GstValidateActionReturn tmpres; + + tmpres = + gst_validate_object_set_property (GST_VALIDATE_REPORTER (scenario), + G_OBJECT (l->data), property, property_value, action->priv->optional); + + if (!tmpres) + ret = tmpres; + } else { + ret = + _check_property (scenario, action, l->data, property, property_value); + } + } + + g_list_free_full (targets, gst_object_unref); + return ret; +} + +static gboolean +_execute_set_debug_threshold (GstValidateScenario * scenario, + GstValidateAction * action) +{ + gchar *str = NULL; + gboolean reset = TRUE; + const gchar *threshold_str; + + threshold_str = + gst_structure_get_string (action->structure, "debug-threshold"); + if (threshold_str == NULL) { + gint threshold; + + if (gst_structure_get_int (action->structure, "debug-threshold", + &threshold)) + threshold_str = str = g_strdup_printf ("%i", threshold); + else + return FALSE; + } + + gst_structure_get_boolean (action->structure, "reset", &reset); + + gst_debug_set_threshold_from_string (threshold_str, reset); + + g_free (str); + + return TRUE; +} + +static gboolean +_execute_emit_signal (GstValidateScenario * scenario, + GstValidateAction * action) +{ + GstElement *target; + const gchar *signal_name; + + target = _get_target_element (scenario, action); + if (target == NULL) { + return FALSE; + } + + signal_name = gst_structure_get_string (action->structure, "signal-name"); + + /* Right now we don't support arguments to signals as there weren't any use + * cases to cover yet but it should be possible to do so */ + g_signal_emit_by_name (target, signal_name, NULL); + + gst_object_unref (target); + return TRUE; +} + +typedef GstFlowReturn (*ChainWrapperFunction) (GstPad * pad, GstObject * parent, + GstBuffer * buffer, gpointer * user_data, gboolean * remove_wrapper); + +typedef struct _ChainWrapperFunctionData +{ + GstPadChainFunction wrapped_chain_func; + gpointer wrapped_chain_data; + GDestroyNotify wrapped_chain_notify; + ChainWrapperFunction wrapper_function; + gpointer wrapper_function_user_data; +} ChainWrapperFunctionData; + +static GstFlowReturn +_pad_chain_wrapper (GstPad * pad, GstObject * parent, GstBuffer * buffer) +{ + ChainWrapperFunctionData *data = pad->chaindata; + GstFlowReturn ret; + gboolean remove_wrapper = FALSE; + + pad->chainfunc = data->wrapped_chain_func; + pad->chaindata = data->wrapped_chain_data; + pad->chainnotify = data->wrapped_chain_notify; + + ret = data->wrapper_function (pad, parent, buffer, + data->wrapper_function_user_data, &remove_wrapper); + + if (!remove_wrapper) { + /* The chain function may have changed during the calling (e.g. if it was + * a nested wrapper that decided to remove itself) so we need to update the + * wrapped function just in case. */ + data->wrapped_chain_func = pad->chainfunc; + data->wrapped_chain_data = pad->chaindata; + data->wrapped_chain_notify = pad->chainnotify; + + /* Restore the wrapper as chain function */ + pad->chainfunc = _pad_chain_wrapper; + pad->chaindata = data; + pad->chainnotify = g_free; + } else + g_free (data); + + return ret; +} + +static void +wrap_pad_chain_function (GstPad * pad, ChainWrapperFunction new_function, + gpointer user_data) +{ + ChainWrapperFunctionData *data = g_new (ChainWrapperFunctionData, 1); + data->wrapped_chain_func = pad->chainfunc; + data->wrapped_chain_data = pad->chaindata; + data->wrapped_chain_notify = pad->chainnotify; + data->wrapper_function = new_function; + data->wrapper_function_user_data = user_data; + + pad->chainfunc = _pad_chain_wrapper; + pad->chaindata = data; + pad->chainnotify = g_free; +} + +static GstFlowReturn +appsrc_push_chain_wrapper (GstPad * pad, GstObject * parent, GstBuffer * buffer, + gpointer * user_data, gboolean * remove_wrapper) +{ + GstValidateAction *action = (GstValidateAction *) user_data; + GstValidateScenario *scenario = gst_validate_action_get_scenario (action); + GstFlowReturn ret; + GST_VALIDATE_SCENARIO_EOS_HANDLING_LOCK (scenario); + ret = pad->chainfunc (pad, parent, buffer); + gst_validate_action_set_done (action); + gst_validate_action_unref (action); + *remove_wrapper = TRUE; + GST_VALIDATE_SCENARIO_EOS_HANDLING_UNLOCK (scenario); + g_object_unref (scenario); + return ret; +} + +static gboolean +structure_get_uint64_permissive (const GstStructure * structure, + const gchar * fieldname, guint64 * dest) +{ + const GValue *original; + GValue transformed = G_VALUE_INIT; + + original = gst_structure_get_value (structure, fieldname); + if (!original) + return FALSE; + + g_value_init (&transformed, G_TYPE_UINT64); + if (!g_value_transform (original, &transformed)) + return FALSE; + + *dest = g_value_get_uint64 (&transformed); + g_value_unset (&transformed); + return TRUE; +} + +static gint +_execute_appsrc_push (GstValidateScenario * scenario, + GstValidateAction * action) +{ + GstElement *target = NULL; + gchar *file_name = NULL; + gchar *file_contents = NULL; + GError *error = NULL; + GstBuffer *buffer; + guint64 offset = 0; + guint64 size = 0, read; + gint push_buffer_ret; + gboolean wait; + GFileInfo *finfo = NULL; + GFile *f = NULL; + GstPad *appsrc_pad = NULL; + GstPad *peer_pad = NULL; + GInputStream *stream = NULL; + GstValidateExecuteActionReturn res; + + /* We will only wait for the the buffer to be pushed if we are in a state + * that allows flow of buffers (>=PAUSED). Otherwise the buffer will just + * be enqueued. */ + wait = scenario->priv->target_state >= GST_STATE_PAUSED; + + target = _get_target_element (scenario, action); + REPORT_UNLESS (target, err, "No element found."); + file_name = + g_strdup (gst_structure_get_string (action->structure, "file-name")); + REPORT_UNLESS (file_name, err, "Missing file-name property."); + + structure_get_uint64_permissive (action->structure, "offset", &offset); + structure_get_uint64_permissive (action->structure, "size", &size); + + f = g_file_new_for_path (file_name); + stream = G_INPUT_STREAM (g_file_read (f, NULL, &error)); + REPORT_UNLESS (!error, err, "Could not open file for action. Error: %s", + error->message); + + if (offset > 0) { + read = g_input_stream_skip (stream, offset, NULL, &error); + REPORT_UNLESS (!error, err, "Could not skip to offset. Error: %s", + error->message); + REPORT_UNLESS (read == offset, err, + "Could not skip to offset, only skipped: %" G_GUINT64_FORMAT, read); + } + + if (size <= 0) { + finfo = + g_file_query_info (f, G_FILE_ATTRIBUTE_STANDARD_SIZE, + G_FILE_QUERY_INFO_NONE, NULL, &error); + + REPORT_UNLESS (!error, err, "Could not query file size. Error: %s", + error->message); + size = g_file_info_get_size (finfo); + } + + file_contents = g_malloc (size); + read = g_input_stream_read (stream, file_contents, size, NULL, &error); + REPORT_UNLESS (!error, err, "Could not read input file. Error: %s", + error->message); + REPORT_UNLESS (read == size, err, + "Could read enough data, only read: %" G_GUINT64_FORMAT, read); + + buffer = gst_buffer_new_wrapped (file_contents, size); + file_contents = NULL; + + { + const GValue *caps_value; + GstCaps *caps = NULL; + caps_value = gst_structure_get_value (action->structure, "caps"); + if (caps_value) { + if (G_VALUE_HOLDS_STRING (caps_value)) { + caps = gst_caps_from_string (g_value_get_string (caps_value)); + REPORT_UNLESS (caps, err, "Invalid caps string: %s", + g_value_get_string (caps_value)); + } else { + caps = gst_caps_copy (gst_value_get_caps (caps_value)); + } + + REPORT_UNLESS (caps, err, "Could not get caps value"); + g_object_set (target, "caps", caps, NULL); + gst_caps_unref (caps); + } + } + + /* We temporarily override the peer pad chain function to finish the action + * once the buffer chain actually ends. */ + appsrc_pad = gst_element_get_static_pad (target, "src"); + peer_pad = gst_pad_get_peer (appsrc_pad); + REPORT_UNLESS (peer_pad, err, "Action failed, pad not linked"); + + wrap_pad_chain_function (peer_pad, appsrc_push_chain_wrapper, action); + + /* Keep the action alive until set done is called. */ + gst_validate_action_ref (action); + + g_signal_emit_by_name (target, "push-buffer", buffer, &push_buffer_ret); + gst_buffer_unref (buffer); + REPORT_UNLESS (push_buffer_ret == GST_FLOW_OK, err, + "push-buffer signal failed in action."); + + if (wait) { + res = GST_VALIDATE_EXECUTE_ACTION_ASYNC; + } else { + gst_validate_printf (NULL, + "Pipeline is not ready to push buffers, interlacing appsrc-push action...\n"); + res = GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING; + } +done: + gst_clear_object (&target); + gst_clear_object (&appsrc_pad); + gst_clear_object (&peer_pad); + g_clear_pointer (&file_name, g_free); + g_clear_pointer (&file_contents, g_free); + g_clear_error (&error); + g_clear_object (&f); + g_clear_object (&finfo); + g_clear_object (&stream); + + return res; + +err: + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + goto done; +} + +static gint +_execute_appsrc_eos (GstValidateScenario * scenario, GstValidateAction * action) +{ + GstElement *target; + gint eos_ret; + + target = _get_target_element (scenario, action); + if (target == NULL) { + gchar *structure_string = gst_structure_to_string (action->structure); + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, "No element found for action: %s", + structure_string); + g_free (structure_string); + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + + g_signal_emit_by_name (target, "end-of-stream", &eos_ret); + if (eos_ret != GST_FLOW_OK) { + gchar *structure_string = gst_structure_to_string (action->structure); + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Failed to emit end-of-stream signal for action: %s", structure_string); + g_free (structure_string); + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + + gst_object_unref (target); + return GST_VALIDATE_EXECUTE_ACTION_OK; +} + +static gint +_execute_flush (GstValidateScenario * scenario, GstValidateAction * action) +{ + GstElement *target; + GstEvent *event; + gboolean reset_time = TRUE; + + target = _get_target_element (scenario, action); + if (target == NULL) { + gchar *structure_string = gst_structure_to_string (action->structure); + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, "No element found for action: %s", + structure_string); + g_free (structure_string); + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + + gst_structure_get_boolean (action->structure, "reset-time", &reset_time); + + event = gst_event_new_flush_start (); + if (!gst_element_send_event (target, event)) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, "FLUSH_START event was not handled"); + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + + event = gst_event_new_flush_stop (reset_time); + if (!gst_element_send_event (target, event)) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, "FLUSH_STOP event was not handled"); + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + + return GST_VALIDATE_EXECUTE_ACTION_OK; +} + +static GstValidateExecuteActionReturn +_execute_disable_plugin (GstValidateScenario * scenario, + GstValidateAction * action) +{ + GstPlugin *plugin; + const gchar *plugin_name; + + plugin_name = gst_structure_get_string (action->structure, "plugin-name"); + + plugin = gst_registry_find_plugin (gst_registry_get (), plugin_name); + + if (plugin == NULL) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, "Could not find plugin to disable: %s", + plugin_name); + + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + + gst_validate_printf (action, "Disabling plugin \"%s\"\n", plugin_name); + gst_registry_remove_plugin (gst_registry_get (), plugin); + + return GST_VALIDATE_EXECUTE_ACTION_OK; +} + +static gboolean +gst_validate_action_setup_repeat (GstValidateScenario * scenario, + GstValidateAction * action) +{ + gchar *repeat_expr; + gchar *error = NULL; + gint repeat, position, i; + + if (!gst_structure_has_field (action->structure, "repeat")) + return TRUE; + + if (gst_structure_get_int (action->structure, "repeat", &repeat)) + goto done; + + if (gst_structure_get_double (action->structure, "repeat", + (gdouble *) & repeat)) + goto done; + + repeat_expr = gst_validate_replace_variables_in_string (action, + scenario->priv->vars, gst_structure_get_string (action->structure, + "repeat"), GST_VALIDATE_STRUCTURE_RESOLVE_VARIABLES_ALL); + if (!repeat_expr) { + gst_validate_error_structure (action, "Invalid value for 'repeat'"); + return FALSE; + } + + repeat = gst_validate_utils_parse_expression (repeat_expr, _set_variable_func, + scenario, &error); + if (error) { + gst_validate_error_structure (action, "Invalid value for 'repeat': %s", + error); + g_free (error); + return FALSE; + } + g_free (repeat_expr); + +done: + gst_structure_remove_field (action->structure, "repeat"); + gst_structure_remove_field (action->priv->main_structure, "repeat"); + + action->repeat = 0; + GST_VALIDATE_ACTION_N_REPEATS (action) = repeat; + + position = g_list_index (scenario->priv->actions, action); + g_assert (position >= 0); + for (i = 1; i < repeat; i++) { + GstValidateAction *copy = _action_copy (action); + + copy->repeat = i; + scenario->priv->actions = + g_list_insert (scenario->priv->actions, copy, position + i); + } + + return TRUE; +} + +static GstValidateExecuteActionReturn +gst_validate_action_default_prepare_func (GstValidateAction * action) +{ + gint i; + GstClockTime tmp; + GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK; + GstValidateActionType *type = gst_validate_get_action_type (action->type); + GstValidateScenario *scenario = gst_validate_action_get_scenario (action); + + _update_well_known_vars (scenario); + if (!gst_validate_action_setup_repeat (scenario, action)) + goto err; + + if (GST_VALIDATE_ACTION_N_REPEATS (action)) + gst_structure_set (scenario->priv->vars, + GST_VALIDATE_ACTION_RANGE_NAME (action) ? + GST_VALIDATE_ACTION_RANGE_NAME (action) : "repeat", G_TYPE_INT, + action->repeat, NULL); + gst_validate_structure_resolve_variables (action, action->structure, + scenario->priv->vars, GST_VALIDATE_STRUCTURE_RESOLVE_VARIABLES_ALL); + for (i = 0; type->parameters[i].name; i++) { + if (type->parameters[i].types + && g_str_has_suffix (type->parameters[i].types, "(GstClockTime)")) + gst_validate_action_get_clocktime (scenario, action, + type->parameters[i].name, &tmp); + } + + +done: + gst_clear_mini_object ((GstMiniObject **) & type); + if (scenario) + gst_object_unref (scenario); + + return res; +err: + res = GST_VALIDATE_EXECUTE_ACTION_ERROR; + goto done; +} + +static GstValidateExecuteActionReturn +gst_validate_set_property_prepare_func (GstValidateAction * action) +{ + action->priv->optional = gst_structure_has_field_typed (action->structure, + "on-all-instances", G_TYPE_BOOLEAN); + + return gst_validate_action_default_prepare_func (action); +} + +static GList * +add_gvalue_to_list_as_struct (gpointer source, GList * list, const GValue * v) +{ + if (G_VALUE_HOLDS_STRING (v)) { + GstStructure *structure = + gst_structure_new_from_string (g_value_get_string (v)); + + if (!structure) + gst_validate_error_structure (source, "Invalid structure: %s", + g_value_get_string (v)); + + return g_list_append (list, structure); + } + + if (GST_VALUE_HOLDS_STRUCTURE (v)) + return g_list_append (list, + gst_structure_copy (gst_value_get_structure (v))); + + + gst_validate_error_structure (source, "Expected a string or a structure," + " got %s instead", gst_value_serialize (v)); + return NULL; +} + +static GList * +gst_validate_utils_get_structures (gpointer source, + GstStructure * str, const gchar * fieldname) +{ + guint i, size; + GList *res = NULL; + const GValue *value = gst_structure_get_value (str, fieldname); + + if (!value) + return NULL; + + if (G_VALUE_HOLDS_STRING (value) || GST_VALUE_HOLDS_STRUCTURE (value)) + return add_gvalue_to_list_as_struct (source, res, value); + + if (!GST_VALUE_HOLDS_LIST (value)) { + g_error ("%s must have type list of structure/string (or a string), " + "e.g. %s={ [struct1, a=val1], [struct2, a=val2] }, got: \"%s\" in %s", + fieldname, fieldname, gst_value_serialize (value), + gst_structure_to_string (str)); + return NULL; + } + + size = gst_value_list_get_size (value); + for (i = 0; i < size; i++) + res = + add_gvalue_to_list_as_struct (source, res, + gst_value_list_get_value (value, i)); + + return res; +} + +static GstValidateAction * +gst_validate_create_subaction (GstValidateScenario * scenario, + GstStructure * lvariables, GstValidateAction * action, + GstStructure * nstruct, gint it, gint max) +{ + GstValidateAction *subaction; + GstValidateActionType *action_type = + _find_action_type (gst_structure_get_name (nstruct)); + + if (!action_type) + gst_validate_error_structure (action, + "Unknown action type: '%s'", gst_structure_get_name (nstruct)); + subaction = gst_validate_action_new (scenario, action_type, nstruct, FALSE); + GST_VALIDATE_ACTION_RANGE_NAME (subaction) = + GST_VALIDATE_ACTION_RANGE_NAME (action); + GST_VALIDATE_ACTION_FILENAME (subaction) = + g_strdup (GST_VALIDATE_ACTION_FILENAME (action)); + GST_VALIDATE_ACTION_DEBUG (subaction) = + g_strdup (GST_VALIDATE_ACTION_DEBUG (action)); + GST_VALIDATE_ACTION_LINENO (subaction) = GST_VALIDATE_ACTION_LINENO (action); + subaction->repeat = it; + subaction->priv->subaction_level = action->priv->subaction_level + 1; + GST_VALIDATE_ACTION_N_REPEATS (subaction) = max; + gst_validate_structure_resolve_variables (subaction, subaction->structure, + lvariables, + GST_VALIDATE_STRUCTURE_RESOLVE_VARIABLES_LOCAL_ONLY | + GST_VALIDATE_STRUCTURE_RESOLVE_VARIABLES_NO_FAILURE | + GST_VALIDATE_STRUCTURE_RESOLVE_VARIABLES_NO_EXPRESSION); + gst_structure_free (nstruct); + + return subaction; +} + +static GstValidateExecuteActionReturn +gst_validate_foreach_prepare (GstValidateAction * action) +{ + gint it, i; + gint min = 0, max = 1, step = 1; + const GValue *it_array = NULL; + GstValidateScenario *scenario; + GList *actions, *tmp; + + scenario = gst_validate_action_get_scenario (action); + g_assert (scenario); + _update_well_known_vars (scenario); + gst_validate_action_setup_repeat (scenario, action); + + GST_VALIDATE_ACTION_RANGE_NAME (action) = NULL; + gst_structure_foreach (action->structure, + (GstStructureForeachFunc) _foreach_find_iterator, action); + + /* Allow using the repeat field here too */ + if (!GST_VALIDATE_ACTION_RANGE_NAME (action) + && !GST_VALIDATE_ACTION_N_REPEATS (action)) + gst_validate_error_structure (action, "Missing range specifier field."); + + if (GST_VALIDATE_ACTION_RANGE_NAME (action)) { + const GValue *it_value = gst_structure_get_value (action->structure, + GST_VALIDATE_ACTION_RANGE_NAME (action)); + + if (GST_VALUE_HOLDS_INT_RANGE (it_value)) { + min = gst_value_get_int_range_min (it_value); + max = gst_value_get_int_range_max (it_value); + step = gst_value_get_int_range_step (it_value); + + if (min % step != 0) + gst_validate_error_structure (action, + "Range min[%d] must be a multiple of step[%d].", min, step); + + if (max % step != 0) + gst_validate_error_structure (action, + "Range max[%d] must be a multiple of step[%d].", max, step); + } else { + it_array = it_value; + max = gst_value_array_get_size (it_array); + } + } else { + min = action->repeat; + max = action->repeat + 1; + } + + actions = gst_validate_utils_get_structures (action, action->structure, + "actions"); + i = g_list_index (scenario->priv->actions, action); + for (it = min; it < max; it = it + step) { + GstStructure *lvariables = gst_structure_new_empty ("vars"); + + if (it_array) + gst_structure_set_value (lvariables, + GST_VALIDATE_ACTION_RANGE_NAME (action), + gst_value_array_get_value (it_array, it)); + + for (tmp = actions; tmp; tmp = tmp->next) { + scenario->priv->actions = g_list_insert (scenario->priv->actions, + gst_validate_create_subaction (scenario, lvariables, action, + gst_structure_copy (tmp->data), it, max), i++); + } + } + g_list_free_full (actions, (GDestroyNotify) gst_structure_free); + + scenario->priv->actions = g_list_remove (scenario->priv->actions, action); + gst_structure_remove_field (action->structure, "actions"); + + gst_object_unref (scenario); + return GST_VALIDATE_EXECUTE_ACTION_DONE; +} + +static gboolean +_check_structure_has_expected_value (GQuark field_id, const GValue * value, + GstStructure * message_struct) +{ + const GValue *v = gst_structure_id_get_value (message_struct, field_id); + + if (!v) { + gst_structure_set (message_struct, "__validate_has_expected_values", + G_TYPE_BOOLEAN, FALSE, NULL); + return FALSE; + } + + if (gst_value_compare (value, v) != GST_VALUE_EQUAL) { + gst_structure_set (message_struct, "__validate_has_expected_values", + G_TYPE_BOOLEAN, FALSE, NULL); + return FALSE; + } + + gst_structure_set (message_struct, "__validate_has_expected_values", + G_TYPE_BOOLEAN, TRUE, NULL); + + return TRUE; +} + +static void +_check_waiting_for_message (GstValidateScenario * scenario, + GstMessage * message) +{ + GstStructure *expected_values = NULL; + GstValidateScenarioPrivate *priv = scenario->priv; + const gchar *message_type; + + if (!priv->wait_message_action) { + GST_LOG_OBJECT (scenario, "Not waiting for message"); + return; + } + + message_type = gst_structure_get_string (priv->wait_message_action->structure, + "message-type"); + + if (g_strcmp0 (message_type, GST_MESSAGE_TYPE_NAME (message))) + return; + + GST_LOG_OBJECT (scenario, " Waiting for %s and got %s", message_type, + GST_MESSAGE_TYPE_NAME (message)); + + gst_structure_get (priv->wait_message_action->structure, "expected-values", + GST_TYPE_STRUCTURE, &expected_values, NULL); + if (expected_values) { + gboolean res = FALSE; + GstStructure *message_struct = + (GstStructure *) gst_message_get_structure (message); + + message_struct = + message_struct ? gst_structure_copy (message_struct) : NULL; + if (!message_struct) { + GST_DEBUG_OBJECT (scenario, + "Waiting for %" GST_PTR_FORMAT " but message has no structure.", + priv->wait_message_action->structure); + return; + } + + gst_structure_set (message_struct, "__validate_has_expected_values", + G_TYPE_BOOLEAN, FALSE, NULL); + gst_structure_foreach (expected_values, + (GstStructureForeachFunc) _check_structure_has_expected_value, + message_struct); + + if (!gst_structure_get_boolean (message_struct, + "__validate_has_expected_values", &res) || !res) { + return; + } + } + + gst_validate_action_set_done (priv->wait_message_action); + _add_execute_actions_gsource (scenario); +} + +static gboolean +streams_list_contain (GList * streams, const gchar * stream_id) +{ + GList *l; + + for (l = streams; l; l = g_list_next (l)) { + GstStream *s = l->data; + + if (!g_strcmp0 (s->stream_id, stream_id)) + return TRUE; + } + + return FALSE; +} + +static void +gst_validate_scenario_check_latency (GstValidateScenario * scenario, + GstElement * pipeline) +{ + GstValidateScenarioPrivate *priv = scenario->priv; + GstQuery *query; + GstClockTime min_latency; + + query = gst_query_new_latency (); + if (!gst_element_query (GST_ELEMENT_CAST (pipeline), query)) { + GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR, + "Failed to perform LATENCY query"); + gst_query_unref (query); + return; + } + + gst_query_parse_latency (query, NULL, &min_latency, NULL); + gst_query_unref (query); + GST_DEBUG_OBJECT (scenario, "Pipeline latency: %" GST_TIME_FORMAT + " max allowed: %" GST_TIME_FORMAT, + GST_TIME_ARGS (min_latency), GST_TIME_ARGS (priv->max_latency)); + + if (priv->max_latency != GST_CLOCK_TIME_NONE && + min_latency > priv->max_latency) { + GST_VALIDATE_REPORT (scenario, CONFIG_LATENCY_TOO_HIGH, + "Pipeline latency is too high: %" GST_TIME_FORMAT " (max allowed %" + GST_TIME_FORMAT ")", GST_TIME_ARGS (min_latency), + GST_TIME_ARGS (priv->max_latency)); + } +} + +static gboolean +gst_validate_scenario_is_flush_seeking (GstValidateScenario * scenario) +{ + GstValidateSeekInformation *seekinfo = scenario->priv->current_seek; + + if (!seekinfo) + return FALSE; + + if (!(seekinfo->flags & GST_SEEK_FLAG_FLUSH)) + return FALSE; + + return seekinfo->action->priv->state == GST_VALIDATE_EXECUTE_ACTION_ASYNC; +} + +static void +gst_validate_scenario_reset (GstValidateScenario * scenario) +{ + /* Reset sink information */ + SCENARIO_LOCK (scenario); + g_list_foreach (scenario->priv->sinks, (GFunc) _reset_sink_information, NULL); + /* Reset current seek */ + scenario->priv->current_seek = NULL; + scenario->priv->current_seqnum = GST_SEQNUM_INVALID; + SCENARIO_UNLOCK (scenario); +} + +typedef struct +{ + GstValidateScenario *scenario; + GstMessage *message; +} MessageData; + +static void +message_data_free (MessageData * d) +{ + gst_message_unref (d->message); + gst_object_unref (d->scenario); + + g_free (d); +} + +static gboolean +handle_bus_message (MessageData * d) +{ + gboolean is_error = FALSE; + GstMessage *message = d->message; + GstValidateScenario *scenario = d->scenario; + GstValidateScenarioPrivate *priv = scenario->priv; + GstElement *pipeline = gst_validate_scenario_get_pipeline (scenario); + + if (!pipeline) { + GST_ERROR_OBJECT (scenario, "No pipeline set anymore!"); + return G_SOURCE_REMOVE; + } + + GST_DEBUG_OBJECT (scenario, "message %" GST_PTR_FORMAT, message); + + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ASYNC_DONE: + if (!gst_validate_scenario_is_flush_seeking (scenario) && + priv->needs_async_done) { + priv->needs_async_done = FALSE; + if (priv->actions && _action_sets_state (priv->actions->data) + && !priv->changing_state) + gst_validate_action_set_done (priv->actions->data); + } + + if (priv->needs_playback_parsing) { + priv->needs_playback_parsing = FALSE; + if (!gst_validate_parse_next_action_playback_time (scenario)) + return G_SOURCE_REMOVE; + } + _add_execute_actions_gsource (scenario); + break; + case GST_MESSAGE_STATE_CHANGED: + { + GstState old_state, state, pending_state; + gboolean reached_state; + + if (!pipeline || GST_MESSAGE_SRC (message) != GST_OBJECT (pipeline)) + break; + + gst_message_parse_state_changed (message, &old_state, &state, + &pending_state); + + reached_state = pending_state == GST_STATE_VOID_PENDING; + + if (old_state == GST_STATE_PAUSED && state == GST_STATE_READY) + gst_validate_scenario_reset (scenario); + + if (reached_state && gst_validate_scenario_is_flush_seeking (scenario)) + gst_validate_action_set_done (priv->current_seek->action); + + if (priv->changing_state && priv->target_state == state) { + priv->changing_state = FALSE; + if (priv->actions && _action_sets_state (priv->actions->data) + && reached_state) + gst_validate_action_set_done (priv->actions->data); + } + + if (old_state == GST_STATE_READY && state == GST_STATE_PAUSED) + _add_execute_actions_gsource (scenario); + + /* GstBin only send a new latency message when reaching PLAYING if + * async-handling=true so check the latency manually. */ + if (state == GST_STATE_PLAYING) + gst_validate_scenario_check_latency (scenario, pipeline); + break; + } + case GST_MESSAGE_ERROR: + is_error = TRUE; + + /* Passthrough */ + case GST_MESSAGE_EOS: + { + GstValidateAction *stop_action; + GstValidateActionType *stop_action_type; + GstStructure *s; + + if (!is_error && priv->ignore_eos) { + GST_INFO_OBJECT (scenario, "Got EOS but ignoring it!"); + goto done; + } + + GST_VALIDATE_SCENARIO_EOS_HANDLING_LOCK (scenario); + { + /* gst_validate_action_set_done() does not finish the action + * immediately. Instead, it posts a task to the main thread to do most + * of the work in _action_set_done(). + * + * While the EOS handling lock guarantees that if an action had to call + * gst_validate_action_set_done() it has done so, it does not guarantee + * that _action_set_done() has been called. + * + * Is it possible that this handler is run before _action_set_done(), so + * we check at this point for actions that have a pending_set_done and + * call it before continuing. */ + GList *actions = g_list_copy (priv->actions); + GList *i; + for (i = actions; i; i = i->next) { + GstValidateAction *action = (GstValidateAction *) i->data; + if (action->priv->pending_set_done) + _action_set_done (action); + } + g_list_free (actions); + } + + if (!is_error) { + priv->got_eos = TRUE; + if (priv->wait_message_action) { + + if (priv->actions && priv->actions->next) { + GST_DEBUG_OBJECT (scenario, + "Waiting for a message and got a next action" + " to execute, letting it a chance!"); + GST_VALIDATE_SCENARIO_EOS_HANDLING_UNLOCK (scenario); + goto done; + } else { + /* Clear current message wait if waiting for EOS */ + _check_waiting_for_message (scenario, message); + } + } + } + + SCENARIO_LOCK (scenario); + /* Make sure that if there is an ASYNC_DONE in the message queue, we do not + take it into account */ + g_list_free_full (priv->seeks, + (GDestroyNotify) gst_validate_seek_information_free); + priv->seeks = NULL; + SCENARIO_UNLOCK (scenario); + + GST_DEBUG_OBJECT (scenario, "Got EOS; generate 'stop' action"); + + stop_action_type = _find_action_type ("stop"); + s = gst_structure_new ("stop", "generated-after-eos", G_TYPE_BOOLEAN, + !is_error, "generated-after-error", G_TYPE_BOOLEAN, is_error, NULL); + stop_action = gst_validate_action_new (scenario, stop_action_type, + s, FALSE); + gst_structure_free (s); + gst_validate_execute_action (stop_action_type, stop_action); + gst_mini_object_unref (GST_MINI_OBJECT (stop_action)); + + GST_VALIDATE_SCENARIO_EOS_HANDLING_UNLOCK (scenario); + break; + } + case GST_MESSAGE_BUFFERING: + { + gint percent; + + gst_message_parse_buffering (message, &percent); + + if (percent == 100) + priv->buffering = FALSE; + else + priv->buffering = TRUE; + break; + } + case GST_MESSAGE_STREAMS_SELECTED: + { + guint i; + GList *streams_selected = NULL; + + for (i = 0; i < gst_message_streams_selected_get_size (message); i++) { + GstStream *stream = + gst_message_streams_selected_get_stream (message, i); + + streams_selected = g_list_append (streams_selected, stream); + } + + /* Is there a pending switch-track action waiting for the new streams to + * be selected? */ + if (priv->pending_switch_track) { + GList *expected, *l; + GstValidateScenario *scenario = + gst_validate_action_get_scenario (priv->pending_switch_track); + + expected = + gst_mini_object_get_qdata (GST_MINI_OBJECT_CAST + (priv->pending_switch_track), ACTION_EXPECTED_STREAM_QUARK); + + if (g_list_length (expected) != g_list_length (streams_selected)) { + GST_VALIDATE_REPORT_ACTION (scenario, priv->pending_switch_track, + SCENARIO_ACTION_EXECUTION_ERROR, + "Was expecting %d selected streams but got %d", + g_list_length (expected), g_list_length (streams_selected)); + goto action_done; + } + + for (l = expected; l; l = g_list_next (l)) { + const gchar *stream_id = l->data; + + if (!streams_list_contain (streams_selected, stream_id)) { + GST_VALIDATE_REPORT_ACTION (scenario, priv->pending_switch_track, + SCENARIO_ACTION_EXECUTION_ERROR, + "Stream %s has not be activated", stream_id); + goto action_done; + } + } + + action_done: + gst_object_unref (scenario); + gst_validate_action_set_done (priv->pending_switch_track); + priv->pending_switch_track = NULL; + } + + g_list_free_full (streams_selected, gst_object_unref); + break; + } + case GST_MESSAGE_LATENCY: + gst_validate_scenario_check_latency (scenario, pipeline); + break; + + case GST_MESSAGE_QOS: + { + guint64 dropped; + + /* Check the maximum allowed when scenario is terminating so the report + * will include the actual number of dropped buffers. */ + gst_message_parse_qos_stats (message, NULL, NULL, &dropped); + if (dropped != -1) + priv->dropped = dropped; + break; + } + case GST_MESSAGE_APPLICATION: + { + const GstStructure *s; + s = gst_message_get_structure (message); + if (gst_structure_has_name (s, "validate-segment")) { + GstValidateSinkInformation *sink_info; + + SCENARIO_LOCK (scenario); + sink_info = + _find_sink_information (scenario, + (GstElement *) GST_MESSAGE_SRC (message)); + + if (sink_info) { + const GValue *segment_value; + const GstSegment *segment; + + GST_DEBUG_OBJECT (scenario, "Got segment update for %s", + GST_ELEMENT_NAME (sink_info->sink)); + sink_info->segment_seqnum = GST_MESSAGE_SEQNUM (message); + segment_value = gst_structure_get_value (s, "segment"); + g_assert (segment_value != NULL); + segment = (const GstSegment *) g_value_get_boxed (segment_value); + gst_segment_copy_into (segment, &sink_info->segment); + _validate_sink_information (scenario); + } + SCENARIO_UNLOCK (scenario); + } + } + default: + break; + } + +done: + gst_object_unref (pipeline); + /* Check if we got the message expected by a wait action */ + _check_waiting_for_message (scenario, message); + + execute_next_action_full (scenario, message); + + return G_SOURCE_REMOVE; +} + +static void +message_cb (GstBus * bus, GstMessage * message, GstValidateScenario * scenario) +{ + MessageData *d = g_new0 (MessageData, 1); + + d->message = gst_message_ref (message); + d->scenario = gst_object_ref (scenario); + + g_main_context_invoke_full (scenario->priv->context, + G_PRIORITY_DEFAULT_IDLE, + (GSourceFunc) handle_bus_message, d, (GDestroyNotify) message_data_free); +} + +static gboolean +_action_type_has_parameter (GstValidateActionType * atype, + const gchar * paramname) +{ + gint i; + + if (!atype->parameters) + return FALSE; + + for (i = 0; atype->parameters[i].name; i++) + if (g_strcmp0 (atype->parameters[i].name, paramname) == 0) + return TRUE; + + return FALSE; +} + +static gboolean +gst_validate_scenario_load_structures (GstValidateScenario * scenario, + GList * structures, gboolean * is_config, gchar * origin_file) +{ + gboolean ret = TRUE; + GList *tmp; + GstValidateScenarioPrivate *priv = scenario->priv; + GList *config; + + *is_config = FALSE; + + if (!structures) { + GST_INFO_OBJECT (scenario, "No structures provided"); + return FALSE; + } + + for (tmp = structures; tmp; tmp = tmp->next) { + GstValidateAction *action; + GstValidateActionType *action_type; + const gchar *type; + gboolean on_clock = FALSE; + GstStructure *structure = (GstStructure *) tmp->data; + + type = gst_structure_get_name (structure); + if (!g_strcmp0 (type, "description") || !g_strcmp0 (type, "meta")) { + const gchar *pipeline_name; + + gst_structure_get_boolean (structure, "is-config", is_config); + gst_structure_get_boolean (structure, "handles-states", + &priv->handles_state); + gst_structure_get_boolean (structure, "ignore-eos", &priv->ignore_eos); + gst_structure_get_boolean (structure, "actions-on-idle", + &priv->execute_on_idle); + + if (!priv->handles_state) + priv->target_state = GST_STATE_PLAYING; + + pipeline_name = gst_structure_get_string (structure, "pipeline-name"); + if (pipeline_name) { + g_free (priv->pipeline_name); + priv->pipeline_name = g_strdup (pipeline_name); + } + + gst_validate_utils_get_clocktime (structure, "max-latency", + &priv->max_latency); + + gst_structure_get_int (structure, "max-dropped", &priv->max_dropped); + scenario->description = gst_structure_copy (structure); + + continue; + } else if (!(action_type = _find_action_type (type))) { + if (gst_structure_has_field (structure, "optional-action-type")) { + GST_INFO_OBJECT (scenario, + "Action type not found %s but marked as not mandatory", type); + continue; + } + + gst_validate_error_structure (structure, + "Unknown action type: '%s'", type); + goto failed; + } + + gst_structure_get_boolean (structure, "on-clock", &on_clock); + if ((!g_strcmp0 (type, "crank-clock") || on_clock) && !priv->clock) + priv->clock = GST_TEST_CLOCK (gst_test_clock_new ()); + + if (action_type->parameters) { + guint i; + + for (i = 0; action_type->parameters[i].name; i++) { + if (action_type->parameters[i].mandatory && + gst_structure_has_field (structure, + action_type->parameters[i].name) == FALSE) { + gst_validate_error_structure (structure, + "Mandatory field '%s' not present in structure: %" GST_PTR_FORMAT, + action_type->parameters[i].name, structure); + goto failed; + } + } + } + + action = gst_validate_action_new (scenario, action_type, structure, TRUE); + if (action->priv->state == GST_VALIDATE_EXECUTE_ACTION_ERROR) { + GST_ERROR_OBJECT (scenario, "Newly created action: %" GST_PTR_FORMAT + " was in error state", structure); + + goto failed; + } + + action->action_number = priv->num_actions++; + + if (action->priv->state == GST_VALIDATE_EXECUTE_ACTION_OK) { + GST_DEBUG_OBJECT (scenario, + "Unrefing action that has already been executed"); + gst_validate_action_unref (action); + action = NULL; + } + } + + /* max-latency and max-dropped can be overridden using config */ + for (config = gst_validate_plugin_get_config (NULL); config; + config = g_list_next (config)) { + GstClockTime max_latency; + + gst_validate_utils_get_clocktime (config->data, "max-latency", + &max_latency); + if (GST_CLOCK_TIME_IS_VALID (max_latency)) + priv->max_latency = max_latency; + + gst_structure_get_int (config->data, "max-dropped", &priv->max_dropped); + } + +done: + g_list_free_full (structures, (GDestroyNotify) gst_structure_free); + + return ret; + +failed: + ret = FALSE; + + goto done; +} + +gchar ** +gst_validate_scenario_get_include_paths (const gchar * relative_scenario) +{ + gint n; + gchar **env_scenariodir; + gchar *scenarios_path = g_strdup (g_getenv ("GST_VALIDATE_SCENARIOS_PATH")); + + if (relative_scenario) { + gchar *relative_dir = g_path_get_dirname (relative_scenario); + gchar *tmp_scenarios_path = + g_strdup_printf ("%s%c%s", scenarios_path, G_SEARCHPATH_SEPARATOR, + relative_dir); + g_free (relative_dir); + + g_free (scenarios_path); + scenarios_path = tmp_scenarios_path; + } + + env_scenariodir = + scenarios_path ? g_strsplit (scenarios_path, G_SEARCHPATH_SEPARATOR_S, + 0) : NULL; + g_free (scenarios_path); + + n = env_scenariodir ? g_strv_length (env_scenariodir) : 0; + env_scenariodir = g_realloc_n (env_scenariodir, n + 3, sizeof (gchar *)); + env_scenariodir[n] = g_build_filename (g_get_user_data_dir (), + "gstreamer-" GST_API_VERSION, "validate", + GST_VALIDATE_SCENARIO_DIRECTORY, NULL); + env_scenariodir[n + 1] = + g_build_filename (GST_DATADIR, "gstreamer-" GST_API_VERSION, "validate", + GST_VALIDATE_SCENARIO_DIRECTORY, NULL); + env_scenariodir[n + 2] = NULL; + + return env_scenariodir; +} + +static gboolean +_load_scenario_file (GstValidateScenario * scenario, + gchar * scenario_file, gboolean * is_config) +{ + return gst_validate_scenario_load_structures (scenario, + gst_validate_utils_structs_parse_from_filename (scenario_file, + (GstValidateGetIncludePathsFunc) + gst_validate_scenario_get_include_paths, NULL), is_config, + scenario_file); +} + +static gboolean +gst_validate_scenario_load (GstValidateScenario * scenario, + const gchar * scenario_name) +{ + gchar **scenarios = NULL; + guint i; + gboolean found_actions = FALSE, is_config, ret = FALSE; + gchar **include_paths = gst_validate_scenario_get_include_paths (NULL); + + if (!scenario_name) + goto invalid_name; + + scenarios = g_strsplit (scenario_name, ":", -1); + + for (i = 0; scenarios[i]; i++) { + guint include_i; + gchar *lfilename = NULL, *tldir = NULL, *scenario_file = NULL; + + ret = FALSE; + + /* First check if the scenario name is not a full path to the + * actual scenario */ + if (g_file_test (scenarios[i], G_FILE_TEST_IS_REGULAR)) { + GST_DEBUG_OBJECT (scenario, "Scenario: %s is a full path to a scenario. " + "Trying to load it", scenarios[i]); + if ((ret = _load_scenario_file (scenario, scenarios[i], &is_config))) { + scenario_file = scenarios[i]; + goto check_scenario; + } + } + + if (g_str_has_suffix (scenarios[i], GST_VALIDATE_SCENARIO_SUFFIX)) + lfilename = g_strdup (scenarios[i]); + else + lfilename = + g_strdup_printf ("%s" GST_VALIDATE_SCENARIO_SUFFIX, scenarios[i]); + + for (include_i = 0; include_paths[include_i]; include_i++) { + tldir = g_build_filename (include_paths[include_i], lfilename, NULL); + if ((ret = _load_scenario_file (scenario, tldir, &is_config))) { + scenario_file = tldir; + break; + } + g_free (tldir); + } + + if (!ret) + goto error; + + /* else check scenario */ + check_scenario: + if (!is_config) { + gchar *scenario_dir = g_path_get_dirname (scenario_file); + gchar *scenario_fname = g_path_get_basename (scenario_file); + gchar **scenario_name = + g_regex_split_simple ("\\.scenario", scenario_fname, 0, 0); + + gst_structure_set (scenario->priv->vars, + "SCENARIO_DIR", G_TYPE_STRING, scenario_dir, + "SCENARIO_NAME", G_TYPE_STRING, scenario_name[0], + "SCENARIO_PATH", G_TYPE_STRING, scenario_file, NULL); + + g_free (scenario_dir); + g_free (scenario_fname); + g_strfreev (scenario_name); + } + + g_free (tldir); + g_free (lfilename); + + if (!is_config) { + if (found_actions == TRUE) + goto one_actions_scenario_max; + else + found_actions = TRUE; + } + } + +done: + + if (include_paths) + g_strfreev (include_paths); + + g_strfreev (scenarios); + + if (ret == FALSE) + gst_validate_abort ("Could not set scenario %s => EXIT\n", scenario_name); + + return ret; + +invalid_name: + { + GST_ERROR ("Invalid name for scenario '%s'", GST_STR_NULL (scenario_name)); + error: + ret = FALSE; + goto done; + } +one_actions_scenario_max: + { + GST_ERROR ("You can set at most only one action scenario. " + "You can have several config scenarios though (a config scenario's " + "file must have is-config=true, and all its actions must be executable " + "at parsing time)."); + ret = FALSE; + goto done; + + } +} + + +static void +gst_validate_scenario_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstValidateScenario *self = GST_VALIDATE_SCENARIO (object); + + switch (prop_id) { + case PROP_RUNNER: + /* we assume the runner is valid as long as this scenario is, + * no ref taken */ + gst_validate_reporter_set_runner (GST_VALIDATE_REPORTER (object), + g_value_get_object (value)); + break; + case PROP_HANDLES_STATE: + g_assert_not_reached (); + break; + case PROP_EXECUTE_ON_IDLE: + self->priv->execute_on_idle = g_value_get_boolean (value); + break; + default: + break; + } +} + +static void +gst_validate_scenario_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstValidateScenario *self = GST_VALIDATE_SCENARIO (object); + + switch (prop_id) { + case PROP_RUNNER: + /* we assume the runner is valid as long as this scenario is, + * no ref taken */ + g_value_take_object (value, + gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (object))); + break; + case PROP_HANDLES_STATE: + g_value_set_boolean (value, self->priv->handles_state); + break; + case PROP_EXECUTE_ON_IDLE: + g_value_set_boolean (value, self->priv->execute_on_idle); + break; + default: + break; + } +} + +static void +gst_validate_scenario_class_init (GstValidateScenarioClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gst_validate_scenario_dispose; + object_class->finalize = gst_validate_scenario_finalize; + + object_class->get_property = gst_validate_scenario_get_property; + object_class->set_property = gst_validate_scenario_set_property; + + g_object_class_install_property (object_class, PROP_RUNNER, + g_param_spec_object ("validate-runner", "VALIDATE Runner", + "The Validate runner to report errors to", + GST_TYPE_VALIDATE_RUNNER, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_HANDLES_STATE, + g_param_spec_boolean ("handles-states", "Handles state", + "True if the application should not handle the first state change. " + "False if it is application responsibility", + FALSE, G_PARAM_READABLE)); + + g_object_class_install_property (object_class, + PROP_EXECUTE_ON_IDLE, + g_param_spec_boolean ("execute-on-idle", + "Force waiting between actions", + "Always execute actions on idle and do not chain them to execute as" + " fast as possible. Setting this property is useful if action" + " execution can lead to the addition of new sources on the same main" + " loop as it provides these new GSource a chance to be dispatched" + " between actions", FALSE, G_PARAM_READWRITE)); + + /** + * GstValidateScenario::done: + * @scenario: The scenario running + * + * Emitted once all actions have been executed + */ + scenario_signals[DONE] = + g_signal_new ("done", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + + /** + * GstValidateScenario::action-done: + * @scenario: The scenario running + * @action: The #GstValidateAction that is done running + * + * Emitted when an action is done. + * + * Since: 1.20 + */ + scenario_signals[ACTION_DONE] = + g_signal_new ("action-done", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, + GST_TYPE_VALIDATE_ACTION); +} + +static void +gst_validate_scenario_init (GstValidateScenario * scenario) +{ + GstValidateScenarioPrivate *priv = scenario->priv = + gst_validate_scenario_get_instance_private (scenario); + + priv->seek_pos_tol = DEFAULT_SEEK_TOLERANCE; + priv->segment_start = 0; + priv->segment_stop = GST_CLOCK_TIME_NONE; + priv->current_seek = NULL; + priv->current_seqnum = GST_SEQNUM_INVALID; + priv->action_execution_interval = 10; + priv->vars = gst_structure_new_empty ("vars"); + priv->needs_playback_parsing = TRUE; + g_weak_ref_init (&scenario->priv->ref_pipeline, NULL); + priv->max_latency = GST_CLOCK_TIME_NONE; + priv->max_dropped = -1; + priv->clock = NULL; + + g_mutex_init (&priv->lock); + + scenario->priv->context = g_main_context_get_thread_default (); + if (!scenario->priv->context) + scenario->priv->context = g_main_context_default (); + g_main_context_ref (scenario->priv->context); +} + +static void +gst_validate_scenario_dispose (GObject * object) +{ + GstValidateScenarioPrivate *priv = GST_VALIDATE_SCENARIO (object)->priv; + + g_weak_ref_clear (&priv->ref_pipeline); + + if (priv->bus) { + gst_bus_remove_signal_watch (priv->bus); + gst_object_unref (priv->bus); + priv->bus = NULL; + } + + gst_object_replace ((GstObject **) & priv->clock, NULL); + + G_OBJECT_CLASS (gst_validate_scenario_parent_class)->dispose (object); +} + +static void +gst_validate_scenario_finalize (GObject * object) +{ + GstValidateScenario *self = GST_VALIDATE_SCENARIO (object); + GstValidateScenarioPrivate *priv = self->priv; + + /* Because g_object_add_weak_pointer() is used, this MUST be on the + * main thread. */ + g_assert (g_main_context_acquire (priv->context)); + g_main_context_release (priv->context); + + g_main_context_unref (priv->context); + priv->context = NULL; + + g_list_free_full (priv->seeks, + (GDestroyNotify) gst_validate_seek_information_free); + g_list_free_full (priv->sinks, + (GDestroyNotify) gst_validate_sink_information_free); + g_list_free_full (priv->actions, (GDestroyNotify) gst_validate_action_unref); + g_list_free_full (priv->non_blocking_running_actions, + (GDestroyNotify) gst_validate_action_unref); + g_list_free_full (priv->on_addition_actions, + (GDestroyNotify) gst_validate_action_unref); + g_free (priv->pipeline_name); + gst_structure_free (priv->vars); + if (self->description) + gst_structure_free (self->description); + g_mutex_clear (&priv->lock); + + G_OBJECT_CLASS (gst_validate_scenario_parent_class)->finalize (object); +} + +static void _element_added_cb (GstBin * bin, GstElement * element, + GstValidateScenario * scenario); +static void _element_removed_cb (GstBin * bin, GstElement * element, + GstValidateScenario * scenario); + +static void +iterate_children (GstValidateScenario * scenario, GstBin * bin) +{ + GstIterator *it; + GValue v = G_VALUE_INIT; + gboolean done = FALSE; + GHashTable *called; /* set of GstElement on which we already called _element_added_cb() */ + + called = g_hash_table_new (NULL, NULL); + it = gst_bin_iterate_elements (bin); + + while (!done) { + switch (gst_iterator_next (it, &v)) { + case GST_ITERATOR_OK:{ + GstElement *child = g_value_get_object (&v); + + if (g_hash_table_lookup (called, child) == NULL) { + _element_added_cb (bin, child, scenario); + g_hash_table_add (called, child); + } + g_value_reset (&v); + } + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (it); + break; + case GST_ITERATOR_ERROR: + case GST_ITERATOR_DONE: + done = TRUE; + } + } + g_value_reset (&v); + gst_iterator_free (it); + g_hash_table_unref (called); +} + +static gboolean +should_execute_action (GstElement * element, GstValidateAction * action) +{ + return gst_validate_element_matches_target (element, action->structure); +} + +/* Returns TRUE if: + * * The element has no parent (pipeline) + * * Or it's a sink*/ +static gboolean +_all_parents_are_sink (GstElement * element) +{ + if (GST_OBJECT_PARENT (element) == NULL) + return TRUE; + + if (!GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_FLAG_SINK)) + return FALSE; + + return _all_parents_are_sink ((GstElement *) GST_OBJECT_PARENT (element)); +} + +static void +_element_removed_cb (GstBin * bin, GstElement * element, + GstValidateScenario * scenario) +{ + GstValidateScenarioPrivate *priv = scenario->priv; + + if (GST_IS_BASE_SINK (element)) { + GstValidateSinkInformation *sink_info; + SCENARIO_LOCK (scenario); + sink_info = _find_sink_information (scenario, element); + if (sink_info) { + GST_DEBUG_OBJECT (scenario, "Removing sink information for %s", + GST_ELEMENT_NAME (element)); + priv->sinks = g_list_remove (priv->sinks, sink_info); + gst_validate_sink_information_free (sink_info); + } + SCENARIO_UNLOCK (scenario); + } +} + +static void +_element_added_cb (GstBin * bin, GstElement * element, + GstValidateScenario * scenario) +{ + GList *tmp; + + GstValidateScenarioPrivate *priv = scenario->priv; + + /* Check if it's an element we track for a set-property action */ + SCENARIO_LOCK (scenario); + tmp = priv->on_addition_actions; + while (tmp) { + GstValidateAction *action = (GstValidateAction *) tmp->data; + + if (action->playback_time != GST_CLOCK_TIME_NONE) + break; + if (g_strcmp0 (action->type, "set-property")) + break; + + GST_DEBUG_OBJECT (bin, "Checking action #%d %p (%s)", action->action_number, + action, action->type); + if (should_execute_action (element, action)) { + GstValidateActionType *action_type; + action_type = _find_action_type (action->type); + GST_DEBUG_OBJECT (element, "Executing set-property action"); + if (gst_validate_execute_action (action_type, action)) { + if (!gst_structure_has_field_typed (action->structure, + "on-all-instances", G_TYPE_BOOLEAN)) { + priv->on_addition_actions = + g_list_remove_link (priv->on_addition_actions, tmp); + gst_mini_object_unref (GST_MINI_OBJECT (action)); + g_list_free (tmp); + tmp = priv->on_addition_actions; + } else { + tmp = tmp->next; + } + } else + tmp = tmp->next; + } else + tmp = tmp->next; + } + + /* If it's a new GstBaseSink, add to list of sink information */ + if (GST_IS_BASE_SINK (element) && _all_parents_are_sink (element)) { + GstValidateSinkInformation *sink_info = + g_new0 (GstValidateSinkInformation, 1); + GST_DEBUG_OBJECT (scenario, "Adding %s to list of tracked sinks", + GST_ELEMENT_NAME (element)); + sink_info->sink = gst_object_ref (element); + priv->sinks = g_list_append (priv->sinks, sink_info); + } + + SCENARIO_UNLOCK (scenario); + + /* If it's a bin, listen to the child */ + if (GST_IS_BIN (element)) { + g_signal_connect (element, "element-added", (GCallback) _element_added_cb, + scenario); + g_signal_connect (element, "element-removed", + (GCallback) _element_removed_cb, scenario); + iterate_children (scenario, GST_BIN (element)); + } +} + +static GstValidateScenario * +gst_validate_scenario_new (GstValidateRunner * + runner, GstElement * pipeline, gchar * scenario_name, GList * structures) +{ + GList *config; + GstValidateScenario *scenario = + g_object_new (GST_TYPE_VALIDATE_SCENARIO, "validate-runner", + runner, NULL); + + if (structures) { + gboolean is_config; + gst_validate_scenario_load_structures (scenario, structures, &is_config, + scenario_name); + } else { + + GST_LOG ("Creating scenario %s", scenario_name); + if (!gst_validate_scenario_load (scenario, scenario_name)) { + g_object_unref (scenario); + + return NULL; + } + } + + if (scenario->priv->pipeline_name && + !g_pattern_match_simple (scenario->priv->pipeline_name, + GST_OBJECT_NAME (pipeline))) { + GST_INFO ("Scenario %s only applies on pipeline %s not %s", + scenario_name, scenario->priv->pipeline_name, + GST_OBJECT_NAME (pipeline)); + + gst_object_unref (scenario); + + return NULL; + } + + gst_validate_printf (NULL, + "**-> Running scenario %s on pipeline %s**\n", scenario_name, + GST_OBJECT_NAME (pipeline)); + + g_weak_ref_init (&scenario->priv->ref_pipeline, pipeline); + if (scenario->priv->clock) { + gst_element_set_clock (pipeline, GST_CLOCK_CAST (scenario->priv->clock)); + gst_pipeline_use_clock (GST_PIPELINE (pipeline), + GST_CLOCK_CAST (scenario->priv->clock)); + } + gst_validate_reporter_set_name (GST_VALIDATE_REPORTER (scenario), + g_filename_display_basename (scenario_name)); + + g_signal_connect (pipeline, "element-added", (GCallback) _element_added_cb, + scenario); + g_signal_connect (pipeline, "element-removed", + (GCallback) _element_removed_cb, scenario); + + iterate_children (scenario, GST_BIN (pipeline)); + + scenario->priv->bus = gst_element_get_bus (pipeline); + gst_bus_add_signal_watch (scenario->priv->bus); + g_signal_connect (scenario->priv->bus, "message", (GCallback) message_cb, + scenario); + + for (config = gst_validate_plugin_get_config (NULL); config; + config = config->next) { + gint interval; + + if (gst_structure_get_uint (config->data, + "scenario-action-execution-interval", + &scenario->priv->action_execution_interval)) { + GST_DEBUG_OBJECT (scenario, "Setting action execution interval to %d", + scenario->priv->action_execution_interval); + if (scenario->priv->action_execution_interval > 0) + scenario->priv->execute_on_idle = TRUE; + break; + } else if (gst_structure_get_int (config->data, + "scenario-action-execution-interval", &interval)) { + if (interval > 0) { + scenario->priv->action_execution_interval = (guint) interval; + scenario->priv->execute_on_idle = TRUE; + GST_DEBUG_OBJECT (scenario, "Setting action execution interval to %d", + scenario->priv->action_execution_interval); + break; + } else { + GST_WARNING_OBJECT (scenario, "Interval is negative: %d", interval); + } + } + } + + if (scenario->priv->handles_state) { + GST_INFO_OBJECT (scenario, "Scenario handles state." + " Starting the get position source"); + _add_execute_actions_gsource (scenario); + } + + scenario->priv->overrides = + gst_validate_override_registry_get_override_for_names + (gst_validate_override_registry_get (), "scenarios", NULL); + + return scenario; +} + +GstValidateScenario * +gst_validate_scenario_from_structs (GstValidateRunner * runner, + GstElement * pipeline, GList * structures, gchar * origin_file) +{ + g_return_val_if_fail (structures, NULL); + + return gst_validate_scenario_new (runner, pipeline, origin_file, structures); +} + +/** + * gst_validate_scenario_factory_create: + * @runner: The #GstValidateRunner to use to report issues + * @pipeline: The pipeline to run the scenario on + * @scenario_name: The name (or path) of the scenario to run + * + * Returns: (transfer full): A #GstValidateScenario or NULL + */ +GstValidateScenario * +gst_validate_scenario_factory_create (GstValidateRunner * + runner, GstElement * pipeline, const gchar * scenario_name) +{ + return gst_validate_scenario_new (runner, pipeline, (gchar *) scenario_name, + NULL); +} + +static gboolean +_add_description (GQuark field_id, const GValue * value, KeyFileGroupName * kfg) +{ + gchar *tmp = gst_value_serialize (value); + gchar *tmpcompress = g_strcompress (tmp); + + g_key_file_set_string (kfg->kf, kfg->group_name, + g_quark_to_string (field_id), tmpcompress); + + g_free (tmpcompress); + g_free (tmp); + + return TRUE; +} + +gboolean +gst_validate_scenario_check_and_set_needs_clock_sync (GList * structures, + GstStructure ** meta) +{ + gboolean needs_clock_sync = FALSE; + GList *tmp; + + for (tmp = structures; tmp; tmp = tmp->next) { + GstStructure *_struct = (GstStructure *) tmp->data; + gboolean is_meta = gst_structure_has_name (_struct, "description") + || gst_structure_has_name (_struct, "meta"); + + if (!is_meta) { + GstValidateActionType *type = + _find_action_type (gst_structure_get_name (_struct)); + + if (type && type->flags & GST_VALIDATE_ACTION_TYPE_NEEDS_CLOCK) + needs_clock_sync = TRUE; + continue; + } + + if (!*meta) + *meta = gst_structure_copy (_struct); + } + + if (needs_clock_sync) { + if (*meta) + gst_structure_set (*meta, "need-clock-sync", G_TYPE_BOOLEAN, TRUE, NULL); + else + *meta = gst_structure_from_string ("description, need-clock-sync=true;", + NULL); + } + + return needs_clock_sync; +} + +static gboolean +_parse_scenario (GFile * f, GKeyFile * kf) +{ + gboolean ret = FALSE; + gchar *path = g_file_get_path (f); + + if (g_str_has_suffix (path, GST_VALIDATE_SCENARIO_SUFFIX)) { + GstStructure *meta = NULL; + GList *tmp, *structures = gst_validate_structs_parse_from_gfile (f, + (GstValidateGetIncludePathsFunc) + gst_validate_scenario_get_include_paths); + + gst_validate_scenario_check_and_set_needs_clock_sync (structures, &meta); + for (tmp = structures; tmp; tmp = tmp->next) + gst_structure_remove_fields (tmp->data, "__lineno__", "__filename__", + "__debug__", NULL); + + if (meta) { + KeyFileGroupName kfg; + + kfg.group_name = g_file_get_path (f); + kfg.kf = kf; + + gst_structure_remove_fields (meta, "__lineno__", "__filename__", + "__debug__", NULL); + gst_structure_foreach (meta, + (GstStructureForeachFunc) _add_description, &kfg); + gst_structure_free (meta); + } else { + g_key_file_set_string (kf, path, "noinfo", "nothing"); + } + g_list_free_full (structures, (GDestroyNotify) gst_structure_free); + + ret = TRUE; + } + + g_free (path); + return ret; +} + +static void +_list_scenarios_in_dir (GFile * dir, GKeyFile * kf) +{ + GFileEnumerator *fenum; + GFileInfo *info; + + fenum = g_file_enumerate_children (dir, G_FILE_ATTRIBUTE_STANDARD_NAME, + G_FILE_QUERY_INFO_NONE, NULL, NULL); + + if (fenum == NULL) + return; + + for (info = g_file_enumerator_next_file (fenum, NULL, NULL); + info; info = g_file_enumerator_next_file (fenum, NULL, NULL)) { + GFile *f = g_file_enumerator_get_child (fenum, info); + + _parse_scenario (f, kf); + gst_object_unref (f); + } + + gst_object_unref (fenum); +} + +gboolean +gst_validate_list_scenarios (gchar ** scenarios, gint num_scenarios, + gchar * output_file) +{ + gchar *result; + gsize datalength; + + GError *err = NULL; + GKeyFile *kf = NULL; + gint res = 0; + const gchar *envvar; + gchar **env_scenariodir = NULL; + gchar *tldir = g_build_filename (g_get_user_data_dir (), + "gstreamer-" GST_API_VERSION, "validate", GST_VALIDATE_SCENARIO_DIRECTORY, + NULL); + GFile *dir = g_file_new_for_path (tldir); + g_free (tldir); + + kf = g_key_file_new (); + if (num_scenarios > 0) { + gint i; + GFile *file; + + for (i = 0; i < num_scenarios; i++) { + file = g_file_new_for_path (scenarios[i]); + if (!_parse_scenario (file, kf)) { + GST_ERROR ("Could not parse scenario: %s", scenarios[i]); + + gst_object_unref (file); + res = 1; + } + } + + goto done; + } + + envvar = g_getenv ("GST_VALIDATE_SCENARIOS_PATH"); + if (envvar) + env_scenariodir = g_strsplit (envvar, ":", 0); + + _list_scenarios_in_dir (dir, kf); + g_object_unref (dir); + + tldir = g_build_filename (GST_DATADIR, "gstreamer-" GST_API_VERSION, + "validate", GST_VALIDATE_SCENARIO_DIRECTORY, NULL); + dir = g_file_new_for_path (tldir); + _list_scenarios_in_dir (dir, kf); + g_object_unref (dir); + g_free (tldir); + + if (env_scenariodir) { + guint i; + + for (i = 0; env_scenariodir[i]; i++) { + dir = g_file_new_for_path (env_scenariodir[i]); + _list_scenarios_in_dir (dir, kf); + g_object_unref (dir); + } + } + + /* Hack to make it work uninstalled */ + dir = g_file_new_for_path ("data/scenarios"); + _list_scenarios_in_dir (dir, kf); + g_object_unref (dir); + +done: + result = g_key_file_to_data (kf, &datalength, &err); + gst_validate_printf (NULL, "All scenarios available:\n%s", result); + + if (output_file && !err) { + if (!g_file_set_contents (output_file, result, datalength, &err)) { + GST_WARNING ("Error writing to file '%s'", output_file); + } + } + + g_free (result); + + if (env_scenariodir) + g_strfreev (env_scenariodir); + + if (err) { + GST_WARNING ("Got error '%s' listing scenarios", err->message); + g_clear_error (&err); + + res = FALSE; + } + + g_key_file_free (kf); + + return res; +} + +static GstValidateActionReturn +check_last_sample_internal (GstValidateScenario * scenario, + GstValidateAction * action, GstElement * sink) +{ + GstSample *sample; + gchar *sum; + GstBuffer *buffer; + const gchar *target_sum; + guint64 frame_number; + GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK; + GstVideoTimeCodeMeta *tc_meta; + + g_object_get (sink, "last-sample", &sample, NULL); + if (sample == NULL) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Could not \"check-last-sample\" as %" GST_PTR_FORMAT + " 'last-sample' property is NULL" + ". MAKE SURE THE 'enable-last-sample' PROPERTY IS SET TO 'TRUE'!", + sink); + + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + + buffer = gst_sample_get_buffer (sample); + target_sum = gst_structure_get_string (action->structure, "checksum"); + if (target_sum) { + GstMapInfo map; + + if (!gst_buffer_map (buffer, &map, GST_MAP_READ)) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Last sample buffer could not be mapped, action can't run."); + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + goto done; + } + sum = g_compute_checksum_for_data (G_CHECKSUM_SHA1, map.data, map.size); + gst_buffer_unmap (buffer, &map); + + if (g_strcmp0 (sum, target_sum)) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Last buffer checksum '%s' is different than the expected one: '%s'", + sum, target_sum); + + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + g_free (sum); + + goto done; + } + + if (!gst_structure_get_uint64 (action->structure, "timecode-frame-number", + &frame_number)) { + gint iframe_number; + + if (!gst_structure_get_int (action->structure, "timecode-frame-number", + &iframe_number)) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "The 'checksum' or 'time-code-frame-number' parameters of the " + "`check-last-sample` action type needs to be specified, none found"); + + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + goto done; + } + + frame_number = (guint64) iframe_number; + } + + tc_meta = gst_buffer_get_video_time_code_meta (buffer); + if (!tc_meta) { + GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR, + "Could not \"check-last-sample\" as the buffer doesn't contain a TimeCode" + " meta"); + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + goto done; + } + + if (gst_video_time_code_frames_since_daily_jam (&tc_meta->tc) != frame_number) { + GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR, + "Last buffer frame number '%" G_GINT64_FORMAT + "' is different than the expected one: '%" G_GINT64_FORMAT "'", + gst_video_time_code_frames_since_daily_jam (&tc_meta->tc), + frame_number); + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + +done: + gst_sample_unref (sample); + return res; +} + +static void +sink_last_sample_notify_cb (GstElement * sink, GParamSpec * arg G_GNUC_UNUSED, + GstValidateAction * action) +{ + GstValidateScenario *scenario = gst_validate_action_get_scenario (action); + + if (!scenario) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "No pipeline anymore, can't check last sample"); + goto done; + } + + check_last_sample_internal (scenario, action, sink); + gst_object_unref (scenario); + +done: + g_signal_handlers_disconnect_by_func (sink, sink_last_sample_notify_cb, + action); + gst_validate_action_set_done (action); + gst_validate_action_unref (action); +} + +static GstValidateExecuteActionReturn +_check_last_sample_value (GstValidateScenario * scenario, + GstValidateAction * action, GstElement * sink) +{ + GstSample *sample; + + /* Connect before checking last sample to avoid a race where + * the sample is set between the time we connect and the time + * the time we get it */ + g_signal_connect (sink, "notify::last-sample", + G_CALLBACK (sink_last_sample_notify_cb), + gst_validate_action_ref (action)); + + g_object_get (sink, "last-sample", &sample, NULL); + if (sample == NULL) + return GST_VALIDATE_EXECUTE_ACTION_ASYNC; + gst_sample_unref (sample); + gst_validate_action_unref (action); + + g_signal_handlers_disconnect_by_func (sink, sink_last_sample_notify_cb, + action); + + return check_last_sample_internal (scenario, action, sink); +} + +static gboolean +_sink_matches_last_sample_specs (GstElement * sink, const gchar * name, + const gchar * fname, GstCaps * sinkpad_caps) +{ + GstCaps *tmpcaps; + GstPad *sinkpad; + GObjectClass *klass = G_OBJECT_GET_CLASS (sink); + GParamSpec *paramspec = g_object_class_find_property (klass, "last-sample"); + + if (!paramspec) + return FALSE; + + if (paramspec->value_type != GST_TYPE_SAMPLE) + return FALSE; + + if (!name && !fname && !sinkpad_caps) + return TRUE; + + if (name && !g_strcmp0 (GST_OBJECT_NAME (sink), name)) + return TRUE; + + if (fname + && !g_strcmp0 (GST_OBJECT_NAME (gst_element_get_factory (sink)), fname)) + return TRUE; + + if (!sinkpad_caps) + return FALSE; + + sinkpad = gst_element_get_static_pad (sink, "sink"); + if (!sinkpad) + return FALSE; + + tmpcaps = gst_pad_get_current_caps (sinkpad); + if (tmpcaps) { + gboolean res = gst_caps_can_intersect (tmpcaps, sinkpad_caps); + + GST_DEBUG_OBJECT (sink, "Matches caps: %" GST_PTR_FORMAT, tmpcaps); + gst_caps_unref (tmpcaps); + + return res; + } else { + GST_INFO_OBJECT (sink, "No caps set yet, can't check it."); + } + + return FALSE; +} + +static GstValidateExecuteActionReturn +_execute_check_last_sample (GstValidateScenario * scenario, + GstValidateAction * action) +{ + GstIterator *it; + GValue data = { 0, }; + gboolean done = FALSE; + GstCaps *caps = NULL; + GstElement *sink = NULL, *tmpelement; + const gchar *name = gst_structure_get_string (action->structure, "sink-name"), + *factory_name = + gst_structure_get_string (action->structure, "sink-factory-name"), + *caps_str = gst_structure_get_string (action->structure, "sinkpad-caps"); + DECLARE_AND_GET_PIPELINE (scenario, action); + + if (caps_str) { + caps = gst_caps_from_string (caps_str); + + g_assert (caps); + } + + it = gst_bin_iterate_recurse (GST_BIN (pipeline)); + while (!done) { + switch (gst_iterator_next (it, &data)) { + case GST_ITERATOR_OK: + tmpelement = g_value_get_object (&data); + if (_sink_matches_last_sample_specs (tmpelement, name, factory_name, + caps)) { + if (sink) { + if (!gst_object_has_as_ancestor (GST_OBJECT (tmpelement), + GST_OBJECT (sink))) { + gchar *tmp = gst_structure_to_string (action->structure); + + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Could not \"check-last-sample\" as several elements were found " + "from describing string: '%s' (%s and %s match)", tmp, + GST_OBJECT_NAME (sink), GST_OBJECT_NAME (tmpelement)); + + g_free (tmp); + } + + gst_object_unref (sink); + } + + sink = gst_object_ref (tmpelement); + } + g_value_reset (&data); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (it); + g_clear_object (&sink); + break; + case GST_ITERATOR_ERROR: + /* Fallthrough */ + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (it); + if (caps) + gst_caps_unref (caps); + + if (!sink) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Could not \"check-last-sample\" as no sink was found from description: '%" + GST_PTR_FORMAT "'", action->structure); + + goto error; + } + + g_clear_object (&pipeline); + return _check_last_sample_value (scenario, action, sink); + +error: + g_clear_object (&sink); + g_clear_object (&pipeline); + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; +} + +static GstPadProbeReturn +_check_is_key_unit_cb (GstPad * pad, GstPadProbeInfo * info, + GstValidateAction * action) +{ + GstValidateScenario *scenario = gst_validate_action_get_scenario (action); + GstClockTime target_running_time = GST_CLOCK_TIME_NONE; + gint count_bufs = 0; + + gst_validate_action_get_clocktime (scenario, action, + "running-time", &target_running_time); + if (GST_IS_EVENT (GST_PAD_PROBE_INFO_DATA (info))) { + if (gst_video_event_is_force_key_unit (GST_PAD_PROBE_INFO_DATA (info))) + gst_structure_set (action->structure, "__priv_seen_event", G_TYPE_BOOLEAN, + TRUE, NULL); + else if (GST_EVENT_TYPE (info->data) == GST_EVENT_SEGMENT + && GST_PAD_DIRECTION (pad) == GST_PAD_SRC) { + const GstSegment *segment = NULL; + + gst_event_parse_segment (info->data, &segment); + gst_structure_set (action->structure, "__priv_segment", GST_TYPE_SEGMENT, + segment, NULL); + } + } else if (GST_IS_BUFFER (GST_PAD_PROBE_INFO_DATA (info)) + && gst_structure_has_field_typed (action->structure, "__priv_seen_event", + G_TYPE_BOOLEAN)) { + GstSegment *segment = NULL; + + if (GST_CLOCK_TIME_IS_VALID (target_running_time)) { + GstClockTime running_time; + + gst_structure_get (action->structure, "__priv_segment", GST_TYPE_SEGMENT, + &segment, NULL); + running_time = + gst_segment_to_running_time (segment, GST_FORMAT_TIME, + GST_BUFFER_TIMESTAMP (info->data)); + + if (running_time < target_running_time) + goto done; + } + + gst_structure_get_int (action->structure, "__priv_count_bufs", &count_bufs); + if (GST_BUFFER_FLAG_IS_SET (GST_PAD_PROBE_INFO_BUFFER (info), + GST_BUFFER_FLAG_DELTA_UNIT)) { + if (count_bufs >= NOT_KF_AFTER_FORCE_KF_EVT_TOLERANCE) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Did not receive a key frame after requested one, " + "at running_time %" GST_TIME_FORMAT " (with a %i " + "frame tolerance)", GST_TIME_ARGS (target_running_time), + NOT_KF_AFTER_FORCE_KF_EVT_TOLERANCE); + + gst_validate_action_set_done (action); + gst_object_unref (scenario); + return GST_PAD_PROBE_REMOVE; + } + + gst_structure_set (action->structure, "__priv_count_bufs", G_TYPE_INT, + count_bufs++, NULL); + } else { + GST_INFO_OBJECT (pad, + "Properly got keyframe after \"force-keyframe\" event " + "with running_time %" GST_TIME_FORMAT " (latency %d frame(s))", + GST_TIME_ARGS (target_running_time), count_bufs); + + gst_structure_remove_fields (action->structure, "__priv_count_bufs", + "__priv_segment", "__priv_seen_event", NULL); + gst_validate_action_set_done (action); + gst_object_unref (scenario); + return GST_PAD_PROBE_REMOVE; + } + } +done: + gst_object_unref (scenario); + + return GST_PAD_PROBE_OK; +} + +static GstValidateExecuteActionReturn +_execute_crank_clock (GstValidateScenario * scenario, + GstValidateAction * action) +{ + GstClockTime expected_diff, expected_time; + GstClockTime prev_time = + gst_clock_get_time (GST_CLOCK (scenario->priv->clock)); + + if (!gst_test_clock_crank (scenario->priv->clock)) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, "Cranking clock failed"); + + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + + if (gst_validate_action_get_clocktime (scenario, action, + "expected-elapsed-time", &expected_diff)) { + GstClockTime elapsed = + gst_clock_get_time (GST_CLOCK (scenario->priv->clock)) - prev_time; + + if (expected_diff != elapsed) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Elapsed time during test clock cranking different than expected," + " waited for %" GST_TIME_FORMAT " instead of the expected %" + GST_TIME_FORMAT, GST_TIME_ARGS (elapsed), + GST_TIME_ARGS (expected_diff)); + + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + } + + if (gst_validate_action_get_clocktime (scenario, action, "expected-time", + &expected_time)) { + GstClockTime time = gst_clock_get_time (GST_CLOCK (scenario->priv->clock)); + + if (expected_time != time) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Clock time after cranking different than expected," + " got %" GST_TIME_FORMAT " instead of the expected %" GST_TIME_FORMAT, + GST_TIME_ARGS (time), GST_TIME_ARGS (expected_time)); + + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + } + + return GST_VALIDATE_EXECUTE_ACTION_OK; +} + +static gboolean +_execute_request_key_unit (GstValidateScenario * scenario, + GstValidateAction * action) +{ + guint count = 0; + gboolean all_headers = FALSE; + gboolean ret = GST_VALIDATE_EXECUTE_ACTION_ASYNC; + GstEvent *event = NULL; + GstQuery *segment_query; + GList *targets = NULL, *tmp; + GstElement *video_encoder = NULL; + GstPad *pad = NULL, *encoder_srcpad = NULL; + GstClockTime running_time = GST_CLOCK_TIME_NONE; + GstSegment segment = { 0, }; + const gchar *direction = gst_structure_get_string (action->structure, + "direction"), *pad_name, *srcpad_name; + + DECLARE_AND_GET_PIPELINE (scenario, action); + + if (gst_structure_get_string (action->structure, "target-element-name")) { + GstElement *target = _get_target_element (scenario, action); + if (target == NULL) + return FALSE; + + targets = g_list_append (targets, target); + } else { + if (!gst_structure_get_string (action->structure, + "target-element-klass") && + !gst_structure_get_string (action->structure, + "target-element-factory-name")) { + gst_structure_set (action->structure, "target-element-klass", + G_TYPE_STRING, "Video/Encoder", NULL); + } + + targets = _get_target_elements_by_klass_or_factory_name (scenario, action); + } + + if (!targets) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Could not find any element from action: %" GST_PTR_FORMAT, + action->structure); + goto fail; + } + + gst_validate_action_get_clocktime (scenario, action, + "running-time", &running_time); + gst_structure_get_boolean (action->structure, "all-headers", &all_headers); + if (!gst_structure_get_uint (action->structure, "count", &count)) { + gst_structure_get_int (action->structure, "count", (gint *) & count); + } + pad_name = gst_structure_get_string (action->structure, "pad"); + srcpad_name = gst_structure_get_string (action->structure, "srcpad"); + if (!srcpad_name) + srcpad_name = "src"; + + for (tmp = targets; tmp; tmp = tmp->next) { + video_encoder = tmp->data; + encoder_srcpad = gst_element_get_static_pad (video_encoder, srcpad_name); + if (!encoder_srcpad) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, "Could not find pad %s", + srcpad_name); + + goto fail; + } + if (g_strcmp0 (direction, "upstream") == 0) { + event = gst_video_event_new_upstream_force_key_unit (running_time, + all_headers, count); + + pad = gst_element_get_static_pad (video_encoder, srcpad_name); + if (!pad) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, "Could not find pad %s", + srcpad_name); + + goto fail; + } + GST_ERROR_OBJECT (encoder_srcpad, "Sending RequestKeyUnit event"); + gst_pad_add_probe (encoder_srcpad, + GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, + (GstPadProbeCallback) _check_is_key_unit_cb, + gst_validate_action_ref (action), + (GDestroyNotify) gst_validate_action_unref); + } else if (g_strcmp0 (direction, "downstream") == 0) { + GstClockTime timestamp = GST_CLOCK_TIME_NONE, + stream_time = GST_CLOCK_TIME_NONE; + + if (!pad_name) + pad_name = "sink"; + + pad = gst_element_get_static_pad (video_encoder, pad_name); + if (!pad) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, "Could not find pad %s", pad_name); + + goto fail; + } + + gst_validate_action_get_clocktime (scenario, action, + "timestamp", ×tamp); + + gst_validate_action_get_clocktime (scenario, action, + "stream-time", &stream_time); + + event = + gst_video_event_new_downstream_force_key_unit (timestamp, stream_time, + running_time, all_headers, count); + + gst_pad_add_probe (pad, + GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, + (GstPadProbeCallback) _check_is_key_unit_cb, + gst_validate_action_ref (action), + (GDestroyNotify) gst_validate_action_unref); + } else { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "request keyunit direction %s invalid (should be in" + " [downstrean, upstream]", direction); + + goto fail; + } + + gst_validate_printf (action, "Sending a \"force key unit\" event %s\n", + direction); + + segment_query = gst_query_new_segment (GST_FORMAT_TIME); + gst_pad_query (encoder_srcpad, segment_query); + + gst_query_parse_segment (segment_query, &(segment.rate), + &(segment.format), (gint64 *) & (segment.start), + (gint64 *) & (segment.stop)); + gst_structure_set (action->structure, "__priv_segment", GST_TYPE_SEGMENT, + &segment, NULL); + + gst_pad_add_probe (encoder_srcpad, + GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, + (GstPadProbeCallback) _check_is_key_unit_cb, + gst_validate_action_ref (action), + (GDestroyNotify) gst_validate_action_unref); + + + if (!gst_pad_send_event (pad, event)) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Could not send \"force key unit\" event %s", direction); + goto fail; + } + + gst_clear_object (&pad); + gst_clear_object (&encoder_srcpad); + } + +done: + g_list_free_full (targets, gst_object_unref); + gst_clear_object (&pad); + gst_clear_object (&encoder_srcpad); + + return ret; + +fail: + ret = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + goto done; +} + +static GstValidateExecuteActionReturn +_execute_stop (GstValidateScenario * scenario, GstValidateAction * action) +{ + GstBus *bus; + GstValidateScenarioPrivate *priv = scenario->priv; + + DECLARE_AND_GET_PIPELINE (scenario, action); + + bus = gst_element_get_bus (pipeline); + SCENARIO_LOCK (scenario); + if (priv->execute_actions_source_id) { + g_source_remove (priv->execute_actions_source_id); + priv->execute_actions_source_id = 0; + } + if (scenario->priv->actions || scenario->priv->non_blocking_running_actions || + scenario->priv->on_addition_actions) { + guint nb_actions = 0; + gchar *actions = g_strdup (""), *tmpconcat; + GList *tmp; + GList *all_actions = g_list_concat (g_list_concat (scenario->priv->actions, + scenario->priv->non_blocking_running_actions), + scenario->priv->on_addition_actions); + + for (tmp = all_actions; tmp; tmp = tmp->next) { + GstValidateAction *remaining_action = (GstValidateAction *) tmp->data; + GstValidateActionType *type; + + if (remaining_action == action) + continue; + + type = _find_action_type (remaining_action->type); + + tmpconcat = actions; + + if (type->flags & GST_VALIDATE_ACTION_TYPE_NO_EXECUTION_NOT_FATAL || + remaining_action->priv->state == GST_VALIDATE_EXECUTE_ACTION_OK || + remaining_action->priv->optional) { + gst_validate_action_unref (remaining_action); + + continue; + } + + nb_actions++; + + actions = g_strdup_printf ("%s\n%*s- `%s` at %s:%d", actions, 20, "", + remaining_action->type, + GST_VALIDATE_ACTION_FILENAME (remaining_action), + GST_VALIDATE_ACTION_LINENO (remaining_action)); + gst_validate_action_unref (remaining_action); + g_free (tmpconcat); + } + g_list_free (all_actions); + scenario->priv->actions = NULL; + scenario->priv->non_blocking_running_actions = NULL; + scenario->priv->on_addition_actions = NULL; + + + if (nb_actions > 0) { + GstClockTime position = GST_CLOCK_TIME_NONE; + + _get_position (scenario, NULL, &position); + GST_VALIDATE_REPORT (scenario, SCENARIO_NOT_ENDED, + "%i actions were not executed: %s (position: %" GST_TIME_FORMAT + ")", nb_actions, actions, GST_TIME_ARGS (position)); + } + g_free (actions); + } + SCENARIO_UNLOCK (scenario); + + gst_validate_scenario_check_dropped (scenario); + + gst_bus_post (bus, + gst_message_new_request_state (GST_OBJECT_CAST (scenario), + GST_STATE_NULL)); + gst_object_unref (bus); + gst_object_unref (pipeline); + + return TRUE; +} + +static gboolean +_action_set_done (GstValidateAction * action) +{ + gchar *repeat_message = NULL; + JsonBuilder *jbuild; + GstValidateScenario *scenario = gst_validate_action_get_scenario (action); + + if (scenario == NULL || !action->priv->pending_set_done) + return G_SOURCE_REMOVE; + + action->priv->execution_duration = + gst_util_get_timestamp () - action->priv->execution_time; + + jbuild = json_builder_new (); + json_builder_begin_object (jbuild); + json_builder_set_member_name (jbuild, "type"); + json_builder_add_string_value (jbuild, "action-done"); + json_builder_set_member_name (jbuild, "action-type"); + json_builder_add_string_value (jbuild, action->type); + json_builder_set_member_name (jbuild, "execution-duration"); + json_builder_add_double_value (jbuild, + ((gdouble) action->priv->execution_duration / GST_SECOND)); + json_builder_end_object (jbuild); + + gst_validate_send (json_builder_get_root (jbuild)); + g_object_unref (jbuild); + + action->priv->pending_set_done = FALSE; + switch (action->priv->state) { + case GST_VALIDATE_EXECUTE_ACTION_ERROR: + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, "Action %s failed", action->type); + case GST_VALIDATE_EXECUTE_ACTION_ASYNC: + case GST_VALIDATE_EXECUTE_ACTION_IN_PROGRESS: + case GST_VALIDATE_EXECUTE_ACTION_NONE: + case GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED: + case GST_VALIDATE_EXECUTE_ACTION_OK: + { + scenario->priv->actions = g_list_remove (scenario->priv->actions, action); + + _check_scenario_is_done (scenario); + + if (!gst_validate_parse_next_action_playback_time (scenario)) { + gst_validate_error_structure (scenario->priv->actions ? scenario-> + priv->actions->data : NULL, + "Could not determine next action playback time!"); + } + + GST_INFO_OBJECT (scenario, "Action %" GST_PTR_FORMAT " is DONE now" + " executing next", action->structure); + + break; + } + case GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING: + break; + } + + if (GST_VALIDATE_ACTION_N_REPEATS (action)) + repeat_message = + g_strdup_printf ("[%d/%d]", action->repeat, + GST_VALIDATE_ACTION_N_REPEATS (action)); + + gst_validate_printf (NULL, + "%*c⇨ Action `%s` at %s:%d done '%s' %s (duration: %" GST_TIME_FORMAT + ")\n\n", (action->priv->subaction_level * 2) - 1, ' ', + gst_structure_get_name (action->priv->main_structure), + GST_VALIDATE_ACTION_FILENAME (action), + GST_VALIDATE_ACTION_LINENO (action), + gst_validate_action_return_get_name (action->priv->state), + repeat_message ? repeat_message : "", + GST_TIME_ARGS (action->priv->execution_duration)); + g_free (repeat_message); + + g_signal_emit (scenario, scenario_signals[ACTION_DONE], 0, action); + if (action->priv->state != GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING) + /* We took the 'scenario' reference... unreffing it now */ + gst_validate_action_unref (action); + + action->priv->state = GST_VALIDATE_EXECUTE_ACTION_DONE; + gst_validate_scenario_execute_next_or_restart_looping (scenario); + gst_object_unref (scenario); + return G_SOURCE_REMOVE; +} + +/* gst_validate_action_set_done: + * @action: The action that is done executing + * + * Sets @action as "done", meaning that the next action can + * now be executed. + */ +void +gst_validate_action_set_done (GstValidateAction * action) +{ + GMainContext *context = action->priv->context; + GstValidateScenario *scenario = gst_validate_action_get_scenario (action); + + action->priv->context = NULL; + if (action->priv->state == GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING) { + GList *item = NULL; + + if (scenario) { + SCENARIO_LOCK (scenario); + item = g_list_find (scenario->priv->non_blocking_running_actions, action); + scenario->priv->non_blocking_running_actions = + g_list_delete_link (scenario->priv->non_blocking_running_actions, + item); + SCENARIO_UNLOCK (scenario); + } + + if (item) + gst_validate_action_unref (action); + } + + g_assert (!action->priv->pending_set_done); + action->priv->pending_set_done = TRUE; + + if (scenario && scenario->priv->wait_message_action == action) + scenario->priv->wait_message_action = NULL; + gst_clear_object (&scenario); + + g_main_context_invoke_full (action->priv->context, + G_PRIORITY_DEFAULT_IDLE, + (GSourceFunc) _action_set_done, + gst_validate_action_ref (action), + (GDestroyNotify) gst_validate_action_unref); + + if (context) + g_main_context_unref (context); +} + +/** + * gst_validate_action_get_scenario: + * @action: The action for which to retrieve the scenario + * + * Retrieve the scenario from which @action is executed. + * + * Returns: (transfer full): The scenario from which the action is being executed. + */ +GstValidateScenario * +gst_validate_action_get_scenario (GstValidateAction * action) +{ + return g_weak_ref_get (&action->priv->scenario); +} + +/** + * gst_validate_register_action_type: + * @type_name: The name of the new action type to add + * @implementer_namespace: The namespace of the implementer of the action type. + * That should always be the name of the GstPlugin as + * retrieved with #gst_plugin_get_name when the action type + * is registered inside a plugin. + * @function: (scope notified): The function to be called to execute the action + * @parameters: (allow-none) (array zero-terminated=1) (element-type GstValidateActionParameter): The #GstValidateActionParameter usable as parameter of the type + * @description: A description of the new type + * @flags: The #GstValidateActionTypeFlags to set on the new action type + * + * Register a new action type to the action type system. If the action type already + * exists, it will be overridden by the new definition + * + * Returns: (transfer none): The newly created action type or the already registered action type + * if it had a higher rank + */ +GstValidateActionType * +gst_validate_register_action_type (const gchar * type_name, + const gchar * implementer_namespace, + GstValidateExecuteAction function, + GstValidateActionParameter * parameters, + const gchar * description, GstValidateActionTypeFlags flags) +{ + GstValidateActionType *type = gst_validate_register_action_type_dynamic (NULL, + type_name, GST_RANK_NONE, function, parameters, description, + flags); + + g_free (type->implementer_namespace); + type->implementer_namespace = g_strdup (implementer_namespace); + + return type; +} + +static void +_free_action_types (GList * _action_types) +{ + g_list_free_full (_action_types, (GDestroyNotify) gst_mini_object_unref); +} + +/** + * gst_validate_register_action_type_dynamic: + * @plugin: (allow-none): The #GstPlugin that register the action type, + * or NULL for a static element. + * @rank: The ranking of that implementation of the action type called + * @type_name. If an action type has been registered with the same + * name with a higher rank, the new implementation will not be used, + * and the already registered action type is returned. + * If the already registered implementation has a lower rank, the + * new implementation will be used and returned. + * @type_name: The name of the new action type to add + * @function: (scope notified): The function to be called to execute the action + * @parameters: (allow-none) (array zero-terminated=1) (element-type GstValidateActionParameter): The #GstValidateActionParameter usable as parameter of the type + * @description: A description of the new type + * @flags: The #GstValidateActionTypeFlags to be set on the new action type + * + * Returns: (transfer none): The newly created action type or the already registered action type + * if it had a higher rank + */ +GstValidateActionType * +gst_validate_register_action_type_dynamic (GstPlugin * plugin, + const gchar * type_name, GstRank rank, + GstValidateExecuteAction function, GstValidateActionParameter * parameters, + const gchar * description, GstValidateActionTypeFlags flags) +{ + GstValidateActionType *tmptype; + GstValidateActionType *type = gst_validate_action_type_new (); + gboolean is_config = IS_CONFIG_ACTION_TYPE (flags); + gint n_params = is_config ? 0 : 2; + + if (parameters) { + for (n_params = 0; parameters[n_params].name != NULL; n_params++); + n_params += 1; + } + + if (n_params) { + type->parameters = g_new0 (GstValidateActionParameter, n_params); + } + + if (parameters) { + memcpy (type->parameters, parameters, + sizeof (GstValidateActionParameter) * (n_params)); + } + + type->prepare = gst_validate_action_default_prepare_func; + type->execute = function; + type->name = g_strdup (type_name); + if (plugin) + type->implementer_namespace = g_strdup (gst_plugin_get_name (plugin)); + else + type->implementer_namespace = g_strdup ("none"); + + type->description = g_strdup (description); + type->flags = flags; + type->rank = rank; + + if ((tmptype = _find_action_type (type_name))) { + if (tmptype->rank <= rank) { + action_types = g_list_remove (action_types, tmptype); + type->overriden_type = tmptype; + } else { + gst_mini_object_unref (GST_MINI_OBJECT (type)); + + type = tmptype; + } + } + + if (type != tmptype) + action_types = g_list_append (action_types, type); + + if (plugin) { + GList *plugin_action_types = g_object_steal_data (G_OBJECT (plugin), + "GstValidatePluginActionTypes"); + + plugin_action_types = g_list_prepend (plugin_action_types, + gst_mini_object_ref (GST_MINI_OBJECT (type))); + + g_object_set_data_full (G_OBJECT (plugin), "GstValidatePluginActionTypes", + plugin_action_types, (GDestroyNotify) _free_action_types); + } + + return type; +} + +GstValidateActionType * +gst_validate_get_action_type (const gchar * type_name) +{ + GstValidateActionType *type = _find_action_type (type_name); + + if (type) + return + GST_VALIDATE_ACTION_TYPE (gst_mini_object_ref (GST_MINI_OBJECT (type))); + + return NULL; +} + +static GList * +gst_validate_list_action_types (void) +{ + return action_types; +} + +/** + * gst_validate_print_action_types: + * @wanted_types: (array length=num_wanted_types): (optional): List of types to be printed + * @num_wanted_types: Length of @wanted_types + * + * Prints the action types details wanted in @wanted_types + * + * Returns: True if all types could be printed + */ +gboolean +gst_validate_print_action_types (const gchar ** wanted_types, + gint num_wanted_types) +{ + GList *tmp; + gint nfound = 0; + gboolean print_all = (num_wanted_types == 1 + && !g_strcmp0 (wanted_types[0], "all")); + + if (print_all) + gst_validate_printf (NULL, "# GstValidate action types"); + + for (tmp = gst_validate_list_action_types (); tmp; tmp = tmp->next) { + GstValidateActionType *atype = (GstValidateActionType *) tmp->data; + gboolean print = print_all; + + if (num_wanted_types) { + gint n; + + for (n = 0; n < num_wanted_types; n++) { + if (g_strcmp0 (atype->name, wanted_types[n]) == 0 || + g_strcmp0 (atype->implementer_namespace, wanted_types[n]) == 0) { + nfound++; + print = TRUE; + + break; + } + } + } else { + print = TRUE; + } + + if (print && num_wanted_types) { + gst_validate_printf (atype, "\n"); + } else if (print) { + gchar *desc = + g_regex_replace (newline_regex, atype->description, -1, 0, "\n ", + 0, + NULL); + + gst_validate_printf (NULL, "\n%s: %s:\n %s\n", + atype->implementer_namespace, atype->name, desc); + g_free (desc); + } + } + + if (!print_all && num_wanted_types && num_wanted_types > nfound) { + return FALSE; + } + + return TRUE; +} + +/** + * gst_validate_scenario_get_actions: + * @scenario: The scenario to retrieve remaining actions for + * + * Get remaining actions from @scenario. + * + * Returns: (transfer full) (element-type GstValidateAction): A list of #GstValidateAction. + */ +GList * +gst_validate_scenario_get_actions (GstValidateScenario * scenario) +{ + GList *ret; + gboolean main_context_acquired; + + main_context_acquired = g_main_context_acquire (g_main_context_default ()); + g_return_val_if_fail (main_context_acquired, NULL); + + ret = g_list_copy_deep (scenario->priv->actions, + (GCopyFunc) gst_validate_action_ref, NULL); + + g_main_context_release (g_main_context_default ()); + + return ret; +} + +/** + * gst_validate_scenario_get_target_state: + * @scenario: The scenario to retrieve the current target state for + * + * Get current target state from @scenario. + * + * Returns: Current target state. + */ +GstState +gst_validate_scenario_get_target_state (GstValidateScenario * scenario) +{ + return scenario->priv->target_state; +} + +void +init_scenarios (void) +{ + GList *tmp; + + register_action_types (); + + for (tmp = gst_validate_plugin_get_config (NULL); tmp; tmp = tmp->next) { + const gchar *action_typename; + GstStructure *plug_conf = (GstStructure *) tmp->data; + + if ((action_typename = gst_structure_get_string (plug_conf, "action"))) { + GstValidateAction *action; + GstValidateActionType *atype = _find_action_type (action_typename); + + if (!atype) { + gst_validate_error_structure (plug_conf, + "[CONFIG ERROR] Action type %s not found", action_typename); + + continue; + } + + + if (atype->flags & GST_VALIDATE_ACTION_TYPE_HANDLED_IN_CONFIG) { + GST_INFO ("Action type %s from configuration files" + " is handled.", action_typename); + continue; + } + + if (!(atype->flags & GST_VALIDATE_ACTION_TYPE_CONFIG) && + !(_action_type_has_parameter (atype, "as-config"))) { + gst_validate_error_structure (plug_conf, + "[CONFIG ERROR] Action '%s' is not a config action", + action_typename); + + continue; + } + + gst_structure_set (plug_conf, "as-config", G_TYPE_BOOLEAN, TRUE, NULL); + gst_structure_set_name (plug_conf, action_typename); + + action = gst_validate_action_new (NULL, atype, plug_conf, FALSE); + gst_validate_action_unref (action); + } + } +} + +void +register_action_types (void) +{ + GstValidateActionType *type; + GST_DEBUG_CATEGORY_INIT (gst_validate_scenario_debug, "gstvalidatescenario", + GST_DEBUG_FG_YELLOW, "Gst validate scenarios"); + + _gst_validate_action_type = gst_validate_action_get_type (); + _gst_validate_action_type_type = gst_validate_action_type_get_type (); + + /* *INDENT-OFF* */ + REGISTER_ACTION_TYPE ("meta", NULL, + ((GstValidateActionParameter []) { + { + .name = "summary", + .description = "Whether the scenario is a config only scenario (ie. explain what it does)", + .mandatory = FALSE, + .types = "string", + .possible_variables = NULL, + .def = "'Nothing'"}, + { + .name = "is-config", + .description = "Whether the scenario is a config only scenario", + .mandatory = FALSE, + .types = "boolean", + .possible_variables = NULL, + .def = "false" + }, + { + .name = "handles-states", + .description = "Whether the scenario handles pipeline state changes from the beginning\n" + "in that case the application should not set the state of the pipeline to anything\n" + "and the scenario action will be executed from the beginning", + .mandatory = FALSE, + .types = "boolean", + .possible_variables = NULL, + .def = "false"}, + { + .name = "seek", + .description = "Whether the scenario executes seek actions or not", + .mandatory = FALSE, + .types = "boolean", + .possible_variables = NULL, + .def = "false" + }, + { + .name = "reverse-playback", + .description = "Whether the scenario plays the stream backward", + .mandatory = FALSE, + .types = "boolean", + .possible_variables = NULL, + .def = "false" + }, + { + .name = "need-clock-sync", + .description = "Whether the scenario needs the execution to be synchronized with the pipeline's\n" + "clock. Letting the user know if it can be used with a 'fakesink sync=false' sink", + .mandatory = FALSE, + .types = "boolean", + .possible_variables = NULL, + .def = "true if some action requires a playback-time false otherwise" + }, + { + .name = "min-media-duration", + .description = "Lets the user know the minimum duration of the stream for the scenario\n" + "to be usable", + .mandatory = FALSE, + .types = "double", + .possible_variables = NULL, + .def = "0.0" + }, + { + .name = "min-audio-track", + .description = "Lets the user know the minimum number of audio tracks the stream needs to contain\n" + "for the scenario to be usable", + .mandatory = FALSE, + .types = "int", + .possible_variables = NULL, + .def = "0" + }, + { + .name = "min-video-track", + .description = "Lets the user know the minimum number of video tracks the stream needs to contain\n" + "for the scenario to be usable", + .mandatory = FALSE, + .types = "int", + .possible_variables = NULL, + .def = "0" + }, + { + .name = "duration", + .description = "Lets the user know the time the scenario needs to be fully executed", + .mandatory = FALSE, + .types = "double, int", + .possible_variables = NULL, + .def = "infinite (GST_CLOCK_TIME_NONE)" + }, + { + .name = "pipeline-name", + .description = "The name of the GstPipeline on which the scenario should be executed.\n" + "It has the same effect as setting the pipeline using pipeline_name->scenario_name.", + .mandatory = FALSE, + .types = "string", + .possible_variables = NULL, + .def = "NULL" + }, + { + .name = "max-latency", + .description = "The maximum latency in nanoseconds allowed for this pipeline.\n" + "It can be overridden using core configuration, like for example by defining the " + "env variable GST_VALIDATE_CONFIG=core,max-latency=33000000", + .mandatory = FALSE, + .types = "double, int", + .possible_variables = NULL, + .def = "infinite (GST_CLOCK_TIME_NONE)" + }, + { + .name = "max-dropped", + .description = "The maximum number of buffers which can be dropped by the QoS system allowed for this pipeline.\n" + "It can be overridden using core configuration, like for example by defining the " + "env variable GST_VALIDATE_CONFIG=core,max-dropped=100", + .mandatory = FALSE, + .types = "int", + .possible_variables = NULL, + .def = "infinite (-1)" + }, + { + .name = "ignore-eos", + .description = "Ignore EOS and keep executing the scenario when it happens.\n By default " + "a 'stop' action is generated one EOS", + .mandatory = FALSE, + .types = "boolean", + .possible_variables = NULL, + .def = "false" + }, + {NULL} + }), + "Scenario metadata.\nNOTE: it used to be called \"description\"", + GST_VALIDATE_ACTION_TYPE_CONFIG); + + REGISTER_ACTION_TYPE ("seek", _execute_seek, + ((GstValidateActionParameter []) { + { + .name = "start", + .description = "The starting value of the seek", + .mandatory = TRUE, + .types = "double or string (GstClockTime)", + .possible_variables = + "`position`: The current position in the stream\n" + "`duration`: The duration of the stream", + NULL + }, + { + .name = "flags", + .description = "The GstSeekFlags to use", + .mandatory = TRUE, + .types = "string describing the GstSeekFlags to set", + NULL, + }, + { + .name = "rate", + .description = "The rate value of the seek", + .mandatory = FALSE, + .types = "double", + .possible_variables = NULL, + .def = "1.0" + }, + { + .name = "start_type", + .description = "The GstSeekType to use for the start of the seek, in:\n" + " [none, set, end]", + .mandatory = FALSE, + .types = "string", + .possible_variables = NULL, + .def = "set" + }, + { + .name = "stop_type", + .description = "The GstSeekType to use for the stop of the seek, in:\n" + " [none, set, end]", + .mandatory = FALSE, + .types = "string", + .possible_variables = NULL, + .def = "set" + }, + { + .name = "stop", + .description = "The stop value of the seek", + .mandatory = FALSE, + .types = "double or string (GstClockTime)", + .possible_variables = + "`position`: The current position in the stream\n" + "`duration`: The duration of the stream", + .def ="GST_CLOCK_TIME_NONE", + }, + {NULL} + }), + "Seeks into the stream. This is an example of a seek happening when the stream reaches 5 seconds\n" + "or 1 eighth of its duration and seeks to 10s or 2 eighths of its duration:\n" + " seek, playback-time=\"min(5.0, (duration/8))\", start=\"min(10, 2*(duration/8))\", flags=accurate+flush", + GST_VALIDATE_ACTION_TYPE_NEEDS_CLOCK + ); + + REGISTER_ACTION_TYPE ("pause", _execute_pause, + ((GstValidateActionParameter []) { + { + .name = "duration", + .description = "The duration during which the stream will be paused", + .mandatory = FALSE, + .types = "double or string (GstClockTime)", + .possible_variables = NULL, + .def = "0.0", + }, + {NULL} + }), + "Sets pipeline to PAUSED. You can add a 'duration'\n" + "parameter so the pipeline goes back to playing after that duration\n" + "(in second)", + GST_VALIDATE_ACTION_TYPE_NEEDS_CLOCK & GST_VALIDATE_ACTION_TYPE_ASYNC); + + REGISTER_ACTION_TYPE ("play", _execute_play, NULL, + "Sets the pipeline state to PLAYING", GST_VALIDATE_ACTION_TYPE_NONE); + + REGISTER_ACTION_TYPE ("stop", _execute_stop, NULL, + "Stops the execution of the scenario. It will post a 'request-state'" + " message on the bus with NULL as a requested state" + " and the application is responsible for stopping itself." + " If you override that action type, make sure to link up.", + GST_VALIDATE_ACTION_TYPE_NO_EXECUTION_NOT_FATAL); + + REGISTER_ACTION_TYPE ("eos", _execute_eos, NULL, + "Sends an EOS event to the pipeline", + GST_VALIDATE_ACTION_TYPE_NO_EXECUTION_NOT_FATAL); + + REGISTER_ACTION_TYPE ("switch-track", _execute_switch_track, + ((GstValidateActionParameter []) { + { + .name = "type", + .description = "Selects which track type to change (can be 'audio', 'video'," + " or 'text').", + .mandatory = FALSE, + .types = "string", + .possible_variables = NULL, + .def = "audio", + }, + { + .name = "index", + .description = "Selects which track of this type to use: it can be either a number,\n" + "which will be the Nth track of the given type, or a number with a '+' or\n" + "'-' prefix, which means a relative change (eg, '+1' means 'next track',\n" + "'-1' means 'previous track')", + .mandatory = FALSE, + .types = "string: to switch track relatively\n" + "int: To use the actual index to use", + .possible_variables = NULL, + .def = "+1", + }, + {NULL} + }), + "The 'switch-track' command can be used to switch tracks." + , GST_VALIDATE_ACTION_TYPE_NONE); + + REGISTER_ACTION_TYPE ("wait", _execute_wait, + ((GstValidateActionParameter []) { + { + .name = "duration", + .description = "the duration while no other action will be executed", + .mandatory = FALSE, + .types = "double or string (GstClockTime)", + NULL}, + { + .name = "target-element-name", + .description = "The name of the GstElement to wait @signal-name on.", + .mandatory = FALSE, + .types = "string" + }, + { + .name = "signal-name", + .description = "The name of the signal to wait for on @target-element-name." + " To ensure that the signal is executed without blocking while waiting for it" + " you can set the field 'non-blocking=true'.", + .mandatory = FALSE, + .types = "string", + NULL + }, + { + .name = "non-blocking", + .description = "**Only for signals**." + " Ensures that the signal is emitted without a blocking waiting.", + .mandatory = FALSE, + .types = "string", + NULL + }, + { + .name = "message-type", + .description = "The name of the message type to wait for (on @target-element-name" + " if specified)", + .mandatory = FALSE, + .types = "string", + NULL + }, + { + .name = "expected-values", + .description = "Expected values in the message structure (valid only when " + "`message-type`). Example: " + "wait, on-client=true, message-type=buffering, expected-values=[values, buffer-percent=100]", + .mandatory = FALSE, + .types = "GstStructure", + NULL + }, + { + .name = "on-clock", + .description = "Wait until the test clock get a new pending entry" + " see #gst_test_clock_wait_for_next_pending_id.", + .mandatory = FALSE, + .types = "boolean", + NULL + }, + {NULL} + }), + "Waits for signal 'signal-name', message 'message-type', or during 'duration' seconds", + GST_VALIDATE_ACTION_TYPE_DOESNT_NEED_PIPELINE); + + REGISTER_ACTION_TYPE ("dot-pipeline", _execute_dot_pipeline, NULL, + "Dots the pipeline (the 'name' property will be used in the dot filename).\n" + "For more information have a look at the GST_DEBUG_BIN_TO_DOT_FILE documentation.\n" + "Note that the GST_DEBUG_DUMP_DOT_DIR env variable needs to be set", + GST_VALIDATE_ACTION_TYPE_NONE); + + REGISTER_ACTION_TYPE ("set-rank", _execute_set_rank_or_disable_feature, + ((GstValidateActionParameter []) { + { + .name = "name", + .description = "The name of a GstFeature or GstPlugin", + .mandatory = TRUE, + .types = "string", + NULL}, + { + .name = "rank", + .description = "The GstRank to set on @name", + .mandatory = TRUE, + .types = "string, int", + NULL}, + {NULL} + }), + "Changes the ranking of a particular plugin feature(s)", + GST_VALIDATE_ACTION_TYPE_CONFIG); + + REGISTER_ACTION_TYPE ("remove-feature", _execute_set_rank_or_disable_feature, + ((GstValidateActionParameter []) { + { + .name = "name", + .description = "The name of a GstFeature or GstPlugin to remove", + .mandatory = TRUE, + .types = "string", + NULL + }, + {NULL} + }), + "Remove a plugin feature(s) or a plugin from the registry", + GST_VALIDATE_ACTION_TYPE_CONFIG); + + REGISTER_ACTION_TYPE ("set-feature-rank", _execute_set_rank_or_disable_feature, + ((GstValidateActionParameter []) { + { + .name = "feature-name", + .description = "The name of a GstFeature", + .mandatory = TRUE, + .types = "string", + NULL}, + { + .name = "rank", + .description = "The GstRank to set on @feature-name", + .mandatory = TRUE, + .types = "string, int", + NULL}, + {NULL} + }), + "Changes the ranking of a particular plugin feature", + GST_VALIDATE_ACTION_TYPE_CONFIG); + + REGISTER_ACTION_TYPE ("set-state", _execute_set_state, + ((GstValidateActionParameter []) { + { + .name = "state", + .description = "A GstState as a string, should be in: \n" + " * ['null', 'ready', 'paused', 'playing']", + .mandatory = TRUE, + .types = "string", + }, + {NULL} + }), + "Changes the state of the pipeline to any GstState", + GST_VALIDATE_ACTION_TYPE_ASYNC & GST_VALIDATE_ACTION_TYPE_NEEDS_CLOCK); + + REGISTER_ACTION_TYPE ("set-vars", _execute_define_vars, + ((GstValidateActionParameter []) { + {NULL} + }), + "Define vars to be used in other actions.\n" + "For example you can define vars for buffer checksum" + " to be used in the \"check-last-sample\" action type as follow:\n\n" + "```\n" + " set-vars, frame1=SomeRandomHash1,frame2=Anotherhash...\n" + " check-last-sample, checksum=frame1\n" + "```\n", + GST_VALIDATE_ACTION_TYPE_NONE); + + GST_TYPE_INTERPOLATION_CONTROL_SOURCE; + GST_TYPE_TRIGGER_CONTROL_SOURCE; + REGISTER_ACTION_TYPE ("set-timed-value-properties", _set_timed_value_property, + ((GstValidateActionParameter []) { + { + .name = "binding-type", + .description = "The name of the type of binding to use", + .types = "string", + .mandatory = FALSE, + .def = "direct", + }, + { + .name = "source-type", + .description = "The name of the type of ControlSource to use", + .types = "string", + .mandatory = FALSE, + .def = "GstInterpolationControlSource", + }, + { + .name = "interpolation-mode", + .description = "The name of the GstInterpolationMode to on the source", + .types = "string", + .mandatory = FALSE, + .def = "linear", + }, + { + .name = "timestamp", + .description = "The timestamp of the keyframe", + .types = "string or float (GstClockTime)", + .mandatory = TRUE, + }, + {NULL} + }), + "Sets GstTimedValue on pads on elements properties using GstControlBindings \n" + "and GstControlSource as defined in the parameters.\n" + "The properties values to set will be defined as:\n\n" + " element-name.padname::property-name=new-value\n\n" + "> NOTE: `.padname` is not needed if setting a property on an element\n\n" + "This action also adds necessary control source/control bindings.\n", + GST_VALIDATE_ACTION_TYPE_NONE); + + REGISTER_ACTION_TYPE ("check-properties", _execute_set_or_check_properties, + ((GstValidateActionParameter []) { + {NULL} + }), + "Check elements and pads properties values.\n" + "The properties values to check will be defined as:\n\n" + " element-name.padname::property-name\n\n" + "> NOTE: `.padname` is not needed if checking an element property\n\n", + GST_VALIDATE_ACTION_TYPE_NONE); + + REGISTER_ACTION_TYPE ("set-properties", _execute_set_or_check_properties, + ((GstValidateActionParameter []) { + {NULL} + }), + "Set elements and pads properties values.\n" + "The properties values to set will be defined as:\n\n" + " element-name.padname::property-name\n\n" + "> NOTE: `.padname` is not needed if checking an element property\n\n", + GST_VALIDATE_ACTION_TYPE_NONE); + + REGISTER_ACTION_TYPE ("set-property", _execute_set_or_check_property, + ((GstValidateActionParameter []) { + { + .name = "target-element-name", + .description = "The name of the GstElement to set a property on", + .mandatory = FALSE, + .types = "string", + NULL + }, + { + .name = "target-element-factory-name", + .description = "The name factory for which to set a property on built elements", + .mandatory = FALSE, + .types = "string", + NULL + }, + { + .name = "target-element-klass", + .description = "The klass of the GstElements to set a property on", + .mandatory = FALSE, + .types = "string", + NULL + }, + { + .name = "property-name", + .description = "The name of the property to set on @target-element-name", + .mandatory = TRUE, + .types = "string", + NULL + }, + { + .name = "property-value", + .description = "The value of @property-name to be set on the element", + .mandatory = TRUE, + .types = "The same type of @property-name", + NULL + }, + { + .name = "on-all-instances", + .description = "Whether to set property on all instances matching " + "the requirements", + .mandatory = FALSE, + .types = "boolean", + NULL + }, + {NULL} + }), + "Sets a property of an element or klass of elements in the pipeline.\n" + "Besides property-name and value, either 'target-element-name' or\n" + "'target-element-klass' needs to be defined", + GST_VALIDATE_ACTION_TYPE_CAN_EXECUTE_ON_ADDITION | + GST_VALIDATE_ACTION_TYPE_CAN_BE_OPTIONAL | + GST_VALIDATE_ACTION_TYPE_HANDLED_IN_CONFIG); + type->prepare = gst_validate_set_property_prepare_func; + + REGISTER_ACTION_TYPE("check-property", _execute_set_or_check_property, + ((GstValidateActionParameter[]) { + { .name = "target-element-name", + .description = "The name of the GstElement to check a property value", + .mandatory = FALSE, + .types = "string", + NULL }, + { .name = "target-element-factory-name", + .description = "The name factory for which to check a property value on built elements", + .mandatory = FALSE, + .types = "string", + NULL }, + { .name = "target-element-klass", + .description = "The klass of the GstElements to check a property on", + .mandatory = FALSE, + .types = "string", + NULL }, + { .name = "property-name", + .description = "The name of the property to set on @target-element-name", + .mandatory = TRUE, + .types = "string", + NULL }, + { .name = "property-value", + .description = "The expected value of @property-name", + .mandatory = TRUE, + .types = "The same type of @property-name", + NULL }, + { NULL } }), + "Check the value of property of an element or klass of elements in the pipeline.\n" + "Besides property-name and value, either 'target-element-name' or\n" + "'target-element-klass' needs to be defined", + GST_VALIDATE_ACTION_TYPE_NONE); + + REGISTER_ACTION_TYPE ("set-debug-threshold", + _execute_set_debug_threshold, + ((GstValidateActionParameter []) + { + { + .name = "debug-threshold", + .description = "String defining debug threshold\n" + "See gst_debug_set_threshold_from_string", + .mandatory = TRUE, + .types = "string"}, + {NULL} + }), + "Sets the debug level to be used, same format as\n" + "setting the GST_DEBUG env variable", + GST_VALIDATE_ACTION_TYPE_NONE); + + REGISTER_ACTION_TYPE ("emit-signal", _execute_emit_signal, + ((GstValidateActionParameter []) + { + { + .name = "target-element-name", + .description = "The name of the GstElement to emit a signal on", + .mandatory = TRUE, + .types = "string" + }, + { + .name = "signal-name", + .description = "The name of the signal to emit on @target-element-name", + .mandatory = TRUE, + .types = "string", + NULL + }, + {NULL} + }), + "Emits a signal to an element in the pipeline", + GST_VALIDATE_ACTION_TYPE_NONE); + + REGISTER_ACTION_TYPE ("appsrc-push", _execute_appsrc_push, + ((GstValidateActionParameter []) + { + { + .name = "target-element-name", + .description = "The name of the appsrc to push data on", + .mandatory = TRUE, + .types = "string" + }, + { + .name = "file-name", + .description = "Relative path to a file whose contents will be pushed as a buffer", + .mandatory = TRUE, + .types = "string" + }, + { + .name = "offset", + .description = "Offset within the file where the buffer will start", + .mandatory = FALSE, + .types = "uint64" + }, + { + .name = "size", + .description = "Number of bytes from the file that will be pushed as a buffer", + .mandatory = FALSE, + .types = "uint64" + }, + { + .name = "caps", + .description = "Caps for the buffer to be pushed", + .mandatory = FALSE, + .types = "caps" + }, + {NULL} + }), + "Queues a buffer in an appsrc. If the pipeline state allows flow of buffers, the next action is not run until the buffer has been pushed.", + GST_VALIDATE_ACTION_TYPE_NONE); + + REGISTER_ACTION_TYPE ("appsrc-eos", _execute_appsrc_eos, + ((GstValidateActionParameter []) + { + { + .name = "target-element-name", + .description = "The name of the appsrc to emit EOS on", + .mandatory = TRUE, + .types = "string" + }, + {NULL} + }), + "Queues a EOS event in an appsrc.", + GST_VALIDATE_ACTION_TYPE_NONE); + + REGISTER_ACTION_TYPE ("flush", _execute_flush, + ((GstValidateActionParameter []) + { + { + .name = "target-element-name", + .description = "The name of the appsrc to flush on", + .mandatory = TRUE, + .types = "string" + }, + { + .name = "reset-time", + .description = "Whether the flush should reset running time", + .mandatory = FALSE, + .types = "boolean", + .def = "TRUE" + }, + {NULL} + }), + "Sends FLUSH_START and FLUSH_STOP events.", + GST_VALIDATE_ACTION_TYPE_NONE); + + REGISTER_ACTION_TYPE ("disable-plugin", _execute_disable_plugin, + ((GstValidateActionParameter []) + { + { + .name = "plugin-name", + .description = "The name of the GstPlugin to disable", + .mandatory = TRUE, + .types = "string" + }, + { + .name = "as-config", + .description = "Execute action as a config action (meaning when loading the scenario)", + .mandatory = FALSE, + .types = "boolean", + .def = "false" + }, + {NULL} + }), + "Disables a GstPlugin", + GST_VALIDATE_ACTION_TYPE_NONE); + + REGISTER_ACTION_TYPE ("check-last-sample", _execute_check_last_sample, + ((GstValidateActionParameter []) { + { + .name = "sink-name", + .description = "The name of the sink element to check sample on.", + .mandatory = FALSE, + .types = "string", + NULL + }, + { + .name = "sink-factory-name", + .description = "The name of the factory of the sink element to check sample on.", + .mandatory = FALSE, + .types = "string", + NULL + }, + { + .name = "sinkpad-caps", + .description = "The caps (as string) of the sink to check.", + .mandatory = FALSE, + .types = "string", + NULL + }, + { + .name = "checksum", + .description = "The reference checksum of the buffer.", + .mandatory = FALSE, + .types = "string", + NULL + }, + { + .name = "timecode-frame-number", + .description = "The frame number of the buffer as specified on its" + " GstVideoTimeCodeMeta", + .mandatory = FALSE, + .types = "string", + NULL + }, + {NULL} + }), + "Checks the last-sample checksum or frame number (set on its " + " GstVideoTimeCodeMeta) on declared Sink element." + " This allows checking the checksum of a buffer after a 'seek' or after a" + " GESTimeline 'commit'" + " for example", + GST_VALIDATE_ACTION_TYPE_NON_BLOCKING); + + REGISTER_ACTION_TYPE ("crank-clock", _execute_crank_clock, + ((GstValidateActionParameter []) { + { + .name = "expected-time", + .description = "Expected clock time after cranking", + .mandatory = FALSE, + .types = "GstClockTime", + NULL + }, + { + .name = "expected-elapsed-time", + .description = "Check time elapsed during the clock cranking", + .mandatory = FALSE, + .types = "GstClockTime", + NULL + }, + {NULL} + }), "Crank the clock, possibly checking how much time was supposed to be waited on the clock" + " and/or the clock running time after the crank." + " Using one `crank-clock` action in a scenario implies that the scenario is driving the " + " clock and a #GstTestClock will be used. The user will need to crank it the number of " + " time required (using the `repeat` parameter comes handy here).", + GST_VALIDATE_ACTION_TYPE_NEEDS_CLOCK); + + REGISTER_ACTION_TYPE ("video-request-key-unit", _execute_request_key_unit, + ((GstValidateActionParameter []) { + { + .name = "direction", + .description = "The direction for the event to travel, should be in\n" + " * [upstream, downstream]", + .mandatory = TRUE, + .types = "string", + NULL + }, + { + .name = "running-time", + .description = "The running_time can be set to request a new key unit at a specific running_time.\n" + "If not set, GST_CLOCK_TIME_NONE will be used so upstream elements will produce a new key unit " + "as soon as possible.", + .mandatory = FALSE, + .types = "double or string", + .possible_variables = "position: The current position in the stream\n" + "duration: The duration of the stream", + NULL + }, + { + .name = "all-headers", + .description = "TRUE to produce headers when starting a new key unit", + .mandatory = FALSE, + .def = "FALSE", + .types = "boolean", + NULL + }, + { + .name = "count", + .description = "integer that can be used to number key units", + .mandatory = FALSE, + .def = "0", + .types = "int", + NULL + }, + { + .name = "target-element-name", + .description = "The name of the GstElement to send a send force-key-unit to", + .mandatory = FALSE, + .types = "string", + NULL + }, + { + .name = "target-element-factory-name", + .description = "The factory name of the GstElements to send a send force-key-unit to", + .mandatory = FALSE, + .types = "string", + NULL + }, + { + .name = "target-element-klass", + .description = "The klass of the GstElements to send a send force-key-unit to", + .mandatory = FALSE, + .def = "Video/Encoder", + .types = "string", + NULL + }, + { + .name = "pad", + .description = "The name of the GstPad to send a send force-key-unit to", + .mandatory = FALSE, + .def = "sink", + .types = "string", + NULL + }, + { + .name = "srcpad", + .description = "The name of the GstPad to send a send force-key-unit to", + .mandatory = FALSE, + .def = "src", + .types = "string", + NULL + }, + {NULL} + }), + "Request a video key unit", FALSE); + + REGISTER_ACTION_TYPE("check-position", _execute_check_position, + ((GstValidateActionParameter[]) { + { .name = "expected-position", + .description = "The expected pipeline position", + .mandatory = TRUE, + .types = "GstClockTime", + NULL }, + {NULL} + }), + "Check current pipeline position.\n", GST_VALIDATE_ACTION_TYPE_NONE); + + REGISTER_ACTION_TYPE("run-command", _run_command, + ((GstValidateActionParameter[]) { + { + .name = "argv", + .description = "The subprocess arguments", + .mandatory = TRUE, + .types = "(string){array,}", + NULL + }, + { + .name = "env", + .description = "Extra environment variables to set", + .mandatory = FALSE, + .types = "GstStructure", + NULL + }, + {NULL} + }), + "Check current pipeline position.\n", + GST_VALIDATE_ACTION_TYPE_CAN_BE_OPTIONAL); + + REGISTER_ACTION_TYPE("foreach", NULL, + ((GstValidateActionParameter[]) { + { .name = "actions", + .description = "The array of actions to repeat", + .mandatory = TRUE, + .types = "strv", + NULL }, + { NULL } }), + "Run actions defined in the `actions` array the number of times specified\n" + " with an iterator parameter passed in. The iterator can be\n" + " a range like :`i=[start, end, step]` or array of values\n" + " such as: `values=`.\n" + "One and only iterator field is required as parameter.", + GST_VALIDATE_ACTION_TYPE_NONE); + type->prepare = gst_validate_foreach_prepare; + + /* Internal actions types to test the validate scenario implementation */ + REGISTER_ACTION_TYPE("priv_check-action-type-calls", + _execute_check_action_type_calls, NULL, NULL, 0); + REGISTER_ACTION_TYPE("priv_check-subaction-level", + _execute_check_subaction_level, NULL, NULL, 0); + /* *INDENT-ON* */ +} + +void +gst_validate_scenario_deinit (void) +{ + _free_action_types (action_types); + action_types = NULL; +} diff --git a/validate/gst/validate/gst-validate-scenario.h b/validate/gst/validate/gst-validate-scenario.h new file mode 100644 index 0000000000..dc8a7e1a4e --- /dev/null +++ b/validate/gst/validate/gst-validate-scenario.h @@ -0,0 +1,425 @@ +/* GStreamer + * Copyright (C) 2013 Thibault Saunier + * + * gst-validate-runner.c - Validate Runner class + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_VALIDATE_SCENARIO_H__ +#define __GST_VALIDATE_SCENARIO_H__ + +#include +#include + +#include "gst-validate-types.h" +#include + +G_BEGIN_DECLS + +#ifndef __GI_SCANNER__ +#define GST_TYPE_VALIDATE_SCENARIO (gst_validate_scenario_get_type ()) +#define GST_VALIDATE_SCENARIO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_VALIDATE_SCENARIO, GstValidateScenario)) +#define GST_VALIDATE_SCENARIO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_VALIDATE_SCENARIO, GstValidateScenarioClass)) +#define GST_IS_VALIDATE_SCENARIO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_VALIDATE_SCENARIO)) +#define GST_IS_VALIDATE_SCENARIO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_VALIDATE_SCENARIO)) +#define GST_VALIDATE_SCENARIO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_VALIDATE_SCENARIO, GstValidateScenarioClass)) +#endif + +typedef struct _GstValidateScenarioPrivate GstValidateScenarioPrivate; +typedef struct _GstValidateActionParameter GstValidateActionParameter; + +/** + * GstValidateActionReturn: + * GST_VALIDATE_EXECUTE_ACTION_ERROR: + * GST_VALIDATE_EXECUTE_ACTION_OK: + * GST_VALIDATE_EXECUTE_ACTION_ASYNC: + * GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED: + * GST_VALIDATE_EXECUTE_ACTION_IN_PROGRESS: + * GST_VALIDATE_EXECUTE_ACTION_NONE: + * GST_VALIDATE_EXECUTE_ACTION_DONE: + */ +typedef enum +{ + GST_VALIDATE_EXECUTE_ACTION_ERROR, + GST_VALIDATE_EXECUTE_ACTION_OK, + GST_VALIDATE_EXECUTE_ACTION_ASYNC, + + /** + * GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING: + * + * The action will be executed asynchronously without blocking further + * actions to be executed + * + * Since: 1.20 + */ + GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING, + + /** + * GST_VALIDATE_EXECUTE_ACTION_INTERLACED: + * + * Deprecated: 1.20: Use #GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING instead. + */ + GST_VALIDATE_EXECUTE_ACTION_INTERLACED = GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING, + GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED, + GST_VALIDATE_EXECUTE_ACTION_IN_PROGRESS, + GST_VALIDATE_EXECUTE_ACTION_NONE, + GST_VALIDATE_EXECUTE_ACTION_DONE, +} GstValidateActionReturn; + +const gchar *gst_validate_action_return_get_name (GstValidateActionReturn r); + +/* TODO 2.0 -- Make it an actual enum type */ +#define GstValidateExecuteActionReturn gint + +/** + * GstValidateExecuteAction: + * @scenario: The #GstValidateScenario from which the @action is executed + * @action: The #GstValidateAction being executed + * + * A function that executes a #GstValidateAction + * + * Returns: a #GstValidateExecuteActionReturn + */ +typedef GstValidateExecuteActionReturn (*GstValidateExecuteAction) (GstValidateScenario * scenario, GstValidateAction * action); + +/** + * GstValidatePrepareAction: + * @action: The #GstValidateAction to prepare before execution + * + * A function that prepares @action so it can be executed right after. + * Most of the time this function is used to parse and set fields with + * equations in the action structure. + * + * Returns: %TRUE if the action could be prepared and is ready to be run + * , %FALSE otherwise + */ +typedef GstValidateExecuteActionReturn (*GstValidatePrepareAction) (GstValidateAction * action); + + +typedef struct _GstValidateActionPrivate GstValidateActionPrivate; + +#define GST_VALIDATE_ACTION_LINENO(action) (((GstValidateAction*) action)->ABI.abi.lineno) +#define GST_VALIDATE_ACTION_FILENAME(action) (((GstValidateAction*) action)->ABI.abi.filename) +#define GST_VALIDATE_ACTION_DEBUG(action) (((GstValidateAction*) action)->ABI.abi.debug) +#define GST_VALIDATE_ACTION_N_REPEATS(action) (((GstValidateAction*) action)->ABI.abi.n_repeats) +#define GST_VALIDATE_ACTION_RANGE_NAME(action) (((GstValidateAction*) action)->ABI.abi.rangename) + +/** + * GstValidateAction: + * @type: The type of the #GstValidateAction, which is the name of the + * GstValidateActionType registered with + * #gst_validate_register_action_type + * @name: The name of the action, set from the user in the scenario + * @structure: the #GstStructure defining the action + * @scenario: The scenario for this action. This is not thread-safe + * and should be accessed exclusively from the main thread. + * If you need to access it from another thread use the + * #gst_validate_action_get_scenario method + * + * The GstValidateAction defined to be executed as part of a scenario + * + * Only access it from the default main context. + */ +struct _GstValidateAction +{ + GstMiniObject mini_object; + + /*< public > */ + const gchar *type; + const gchar *name; + GstStructure *structure; + + /* < private > */ + guint action_number; + gint repeat; + GstClockTime playback_time; + + GstValidateActionPrivate *priv; + + union { + gpointer _gst_reserved[GST_PADDING_LARGE - 1]; /* ->priv */ + struct { + gint lineno; + gchar *filename; + gchar *debug; + gint n_repeats; + const gchar *rangename; + } abi; + } ABI; +}; + +GST_VALIDATE_API +void gst_validate_action_set_done (GstValidateAction *action); +GST_VALIDATE_API +GstValidateScenario * gst_validate_action_get_scenario (GstValidateAction *action); +GST_VALIDATE_API +GstValidateAction * gst_validate_action_new (GstValidateScenario * scenario, + GstValidateActionType * action_type, + GstStructure *structure, + gboolean add_to_lists); +GST_VALIDATE_API +GstValidateAction* gst_validate_action_ref (GstValidateAction * action); +GST_VALIDATE_API +void gst_validate_action_unref (GstValidateAction * action); + +#ifndef __GI_SCANNER__ +#define GST_TYPE_VALIDATE_ACTION (gst_validate_action_get_type ()) +#define GST_IS_VALIDATE_ACTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_VALIDATE_ACTION)) +#define GST_VALIDATE_ACTION_GET_TYPE(obj) ((GstValidateActionType*)gst_validate_get_action_type(((GstValidateAction*)obj)->type)) +#endif + +GST_VALIDATE_API +GType gst_validate_action_get_type (void); + +/** + * GstValidateActionTypeFlags: + * @GST_VALIDATE_ACTION_TYPE_NONE: No special flag + * @GST_VALIDATE_ACTION_TYPE_CONFIG: The action is a config + * @GST_VALIDATE_ACTION_TYPE_ASYNC: The action can be executed ASYNC + * @GST_VALIDATE_ACTION_TYPE_CAN_EXECUTE_ON_ADDITION: The action will be executed on 'element-added' + * for a particular element type if no playback-time + * is specified + * @GST_VALIDATE_ACTION_TYPE_NEEDS_CLOCK: The pipeline will need to be synchronized with the clock + * for that action type to be used. + * @GST_VALIDATE_ACTION_TYPE_NO_EXECUTION_NOT_FATAL: Do not consider the non execution of the action + * as a fatal error. + * @GST_VALIDATE_ACTION_TYPE_CAN_BE_OPTIONAL: The action can use the 'optional' keyword. Such action + * instances will have the #GST_VALIDATE_ACTION_TYPE_NO_EXECUTION_NOT_FATAL + * flag set and won't be considered as fatal if they fail. + * @GST_VALIDATE_ACTION_TYPE_HANDLED_IN_CONFIG: The action can be used in config files even if it is not strictly a config + * action (ie. it needs a scenario to run). + */ +typedef enum +{ + GST_VALIDATE_ACTION_TYPE_NONE = 0, + GST_VALIDATE_ACTION_TYPE_CONFIG = 1 << 1, + GST_VALIDATE_ACTION_TYPE_ASYNC = 1 << 2, + GST_VALIDATE_ACTION_TYPE_NON_BLOCKING = 1 << 3, + + /** + * GST_VALIDATE_ACTION_TYPE_INTERLACED: + * + * Deprecated: 1.20: Use #GST_VALIDATE_ACTION_TYPE_NON_BLOCKING instead. + */ + GST_VALIDATE_ACTION_TYPE_INTERLACED = 1 << 3, + + /** + * GST_VALIDATE_ACTION_TYPE_NON_BLOCKING: + * + * The action can be executed asynchronously but without blocking further + * actions execution. + * + * Since: 1.20 + */ + GST_VALIDATE_ACTION_TYPE_CAN_EXECUTE_ON_ADDITION = 1 << 4, + GST_VALIDATE_ACTION_TYPE_NEEDS_CLOCK = 1 << 5, + GST_VALIDATE_ACTION_TYPE_NO_EXECUTION_NOT_FATAL = 1 << 6, + GST_VALIDATE_ACTION_TYPE_CAN_BE_OPTIONAL = 1 << 7, + GST_VALIDATE_ACTION_TYPE_DOESNT_NEED_PIPELINE = 1 << 8, + GST_VALIDATE_ACTION_TYPE_HANDLED_IN_CONFIG = 1 << 9, +} GstValidateActionTypeFlags; + +typedef struct _GstValidateActionTypePrivate GstValidateActionTypePrivate; + +/** + * GstValidateActionType: + * @name: The name of the new action type to add + * @implementer_namespace: The namespace of the implementer of the action type + * @execute: The function to be called to execute the action + * @parameters: (allow-none) (array zero-terminated=1) (element-type GstValidateActionParameter): The #GstValidateActionParameter usable as parameter of the type + * @description: A description of the new type + * @flags: The flags of the action type + */ +struct _GstValidateActionType +{ + GstMiniObject mini_object; + + gchar *name; + gchar *implementer_namespace; + + GstValidatePrepareAction prepare; + GstValidateExecuteAction execute; + + GstValidateActionParameter *parameters; + + gchar *description; + GstValidateActionTypeFlags flags; + + GstRank rank; + + GstValidateActionType *overriden_type; + GstValidateActionTypePrivate* priv; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING_LARGE - sizeof (GstRank) - 2]; +}; + +#ifndef __GI_SCANNER__ +#define GST_TYPE_VALIDATE_ACTION_TYPE (gst_validate_action_type_get_type ()) +#define GST_IS_VALIDATE_ACTION_TYPE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_VALIDATE_ACTION_TYPE)) +#define GST_VALIDATE_ACTION_TYPE(obj) ((GstValidateActionType*) obj) +#endif + +GST_VALIDATE_API +GType gst_validate_action_type_get_type (void); + +GST_VALIDATE_API +gboolean gst_validate_print_action_types (const gchar ** wanted_types, gint num_wanted_types); + +/** + * GstValidateActionParameter: + * @name: The name of the parameter + * @description: The description of the parameter + * @mandatory: Whether the parameter is mandatory for + * a specific action type + * @types: The types the parameter can take described as a + * string. It can be precisely describing how the typing works + * using '\n' between the various acceptable types. + * NOTE: The types should end with `(GstClockTime)` if its final + * type is a GstClockTime, this way it will be processed when preparing + * the actions. + * @possible_variables: The name of the variables that can be + * used to compute the value of the parameter. + * For example for the start value of a seek + * action, we will accept to take 'duration' + * which will be replace by the total duration + * of the stream on which the action is executed. + * @def: The default value of a parameter as a string, should be %NULL + * for mandatory streams. + */ +struct _GstValidateActionParameter +{ + const gchar *name; + const gchar *description; + gboolean mandatory; + const gchar *types; + const gchar *possible_variables; + const gchar *def; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +struct _GstValidateScenarioClass +{ + GstObjectClass parent_class; + + /*< public >*/ + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +/** + * GstValidateScenario: + */ +struct _GstValidateScenario +{ + GstObject parent; + + /*< public >*/ + GstStructure *description; + + /*< private >*/ + GstValidateScenarioPrivate *priv; + + union { + gpointer _gst_reserved[GST_PADDING]; + struct { + GMutex eos_handling_lock; + } abi; + } ABI; +}; + +/* Some actions may trigger EOS during their execution. Unlocked this + * could cause a race condition as the main thread may terminate the test + * in response to the EOS message in the bus while the action is still + * going in a different thread. + * To avoid this, the handling of the EOS message is protected with this + * lock. Actions expecting to cause an EOS can hold the lock for their + * duration so that they are guaranteed to finish before the EOS + * terminates the test. */ +#define GST_VALIDATE_SCENARIO_EOS_HANDLING_LOCK(scenario) (g_mutex_lock(&(scenario)->ABI.abi.eos_handling_lock)) +#define GST_VALIDATE_SCENARIO_EOS_HANDLING_UNLOCK(scenario) (g_mutex_unlock(&(scenario)->ABI.abi.eos_handling_lock)) + +GST_VALIDATE_API +GType gst_validate_scenario_get_type (void); + +GST_VALIDATE_API +GstValidateScenario * gst_validate_scenario_factory_create (GstValidateRunner *runner, + GstElement *pipeline, + const gchar *scenario_name); +GST_VALIDATE_API gboolean +gst_validate_list_scenarios (gchar **scenarios, + gint num_scenarios, + gchar * output_file); + +GST_VALIDATE_API GstValidateActionType * +gst_validate_get_action_type (const gchar *type_name); + +GST_VALIDATE_API GstValidateActionType * +gst_validate_register_action_type (const gchar *type_name, + const gchar *implementer_namespace, + GstValidateExecuteAction function, + GstValidateActionParameter * parameters, + const gchar *description, + GstValidateActionTypeFlags flags); + +GST_VALIDATE_API GstValidateActionType * +gst_validate_register_action_type_dynamic (GstPlugin *plugin, + const gchar * type_name, + GstRank rank, + GstValidateExecuteAction function, + GstValidateActionParameter * parameters, + const gchar * description, + GstValidateActionTypeFlags flags); + + +GST_VALIDATE_API +gboolean gst_validate_action_get_clocktime (GstValidateScenario * scenario, + GstValidateAction *action, + const gchar * name, + GstClockTime * retval); + +GST_VALIDATE_API GstValidateExecuteActionReturn +gst_validate_scenario_execute_seek (GstValidateScenario *scenario, + GstValidateAction *action, + gdouble rate, + GstFormat format, + GstSeekFlags flags, + GstSeekType start_type, + GstClockTime start, + GstSeekType stop_type, + GstClockTime stop); + +GST_VALIDATE_API GList * +gst_validate_scenario_get_actions (GstValidateScenario *scenario); +GST_VALIDATE_API GstValidateExecuteActionReturn +gst_validate_execute_action (GstValidateActionType * action_type, + GstValidateAction * action); + +GST_VALIDATE_API GstState +gst_validate_scenario_get_target_state (GstValidateScenario *scenario); + +GST_VALIDATE_API GstElement * +gst_validate_scenario_get_pipeline (GstValidateScenario * scenario); + +GST_VALIDATE_API +void gst_validate_scenario_deinit (void); + +G_END_DECLS + +#endif /* __GST_VALIDATE_SCENARIOS__ */ diff --git a/validate/gst/validate/gst-validate-types.h b/validate/gst/validate/gst-validate-types.h new file mode 100644 index 0000000000..6ba6c20924 --- /dev/null +++ b/validate/gst/validate/gst-validate-types.h @@ -0,0 +1,30 @@ +/* GStreamer + * + * Copyright (C) 2015 Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_VALIDATE_TYPES_H__ +#define __GST_VALIDATE_TYPES_H__ + +typedef struct _GstValidateScenario GstValidateScenario; +typedef struct _GstValidateScenarioClass GstValidateScenarioClass; + +typedef struct _GstValidateAction GstValidateAction; +typedef struct _GstValidateActionType GstValidateActionType; + +#endif /* __GST_VALIDATE_TYPES_ */ diff --git a/validate/gst/validate/gst-validate-utils.c b/validate/gst/validate/gst-validate-utils.c new file mode 100644 index 0000000000..2d3c0e13ec --- /dev/null +++ b/validate/gst/validate/gst-validate-utils.c @@ -0,0 +1,1593 @@ +/* GStreamer + * + * Copyright (C) 2013 Thibault Saunier + * + * gst-validate-utils.c - Some utility functions + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#ifdef G_OS_UNIX +#include +#include +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "gst-validate-utils.h" +#include "gst-validate-internal.h" +#include "validate.h" +#include + +#define PARSER_BOOLEAN_EQUALITY_THRESHOLD (1e-10) +#define PARSER_MAX_TOKEN_SIZE 256 +#define PARSER_MAX_ARGUMENT_COUNT 10 + +static GRegex *_variables_regex = NULL; +static GstStructure *global_vars = NULL; + +static GQuark debug_quark = 0; +static GQuark lineno_quark = 0; +static GQuark filename_quark = 0; + +typedef struct +{ + const gchar *str; + gint len; + gint pos; + jmp_buf err_jmp_buf; + const gchar *error; + void *user_data; + GstValidateParseVariableFunc variable_func; +} MathParser; + +static gdouble _read_power (MathParser * parser); + +static void +_error (MathParser * parser, const gchar * err) +{ + parser->error = err; + longjmp (parser->err_jmp_buf, 1); +} + +static gchar +_peek (MathParser * parser) +{ + if (parser->pos < parser->len) + return parser->str[parser->pos]; + _error (parser, "Tried to read past end of string!"); + return '\0'; +} + +static gchar +_peek_n (MathParser * parser, gint n) +{ + if (parser->pos + n < parser->len) + return parser->str[parser->pos + n]; + _error (parser, "Tried to read past end of string!"); + return '\0'; +} + +static gchar +_next (MathParser * parser) +{ + if (parser->pos < parser->len) + return parser->str[parser->pos++]; + _error (parser, "Tried to read past end of string!"); + return '\0'; +} + +static gdouble +_read_double (MathParser * parser) +{ + gchar c, token[PARSER_MAX_TOKEN_SIZE]; + gint pos = 0; + gdouble val = 0.0; + + c = _peek (parser); + if (c == '+' || c == '-') + token[pos++] = _next (parser); + + while (isdigit (_peek (parser))) + token[pos++] = _next (parser); + + c = _peek (parser); + if (c == '.') + token[pos++] = _next (parser); + + while (isdigit (_peek (parser))) + token[pos++] = _next (parser); + + c = _peek (parser); + if (c == 'e' || c == 'E') { + token[pos++] = _next (parser); + + c = _peek (parser); + if (c == '+' || c == '-') { + token[pos++] = _next (parser); + } + } + + while (isdigit (_peek (parser))) + token[pos++] = _next (parser); + + token[pos] = '\0'; + + if (pos == 0 || sscanf (token, "%lf", &val) != 1) + _error (parser, "Failed to read real number"); + + return val; +} + +static gdouble +_read_term (MathParser * parser) +{ + gdouble v0; + gchar c; + + v0 = _read_power (parser); + c = _peek (parser); + + while (c == '*' || c == '/') { + _next (parser); + if (c == '*') { + v0 *= _read_power (parser); + } else if (c == '/') { + v0 /= _read_power (parser); + } + c = _peek (parser); + } + return v0; +} + +static gdouble +_read_expr (MathParser * parser) +{ + gdouble v0 = 0.0; + gchar c; + + c = _peek (parser); + if (c == '+' || c == '-') { + _next (parser); + if (c == '+') + v0 += _read_term (parser); + else if (c == '-') + v0 -= _read_term (parser); + } else { + v0 = _read_term (parser); + } + + c = _peek (parser); + while (c == '+' || c == '-') { + _next (parser); + if (c == '+') { + v0 += _read_term (parser); + } else if (c == '-') { + v0 -= _read_term (parser); + } + + c = _peek (parser); + } + + return v0; +} + +static gdouble +_read_boolean_comparison (MathParser * parser) +{ + gchar c, oper[] = { '\0', '\0', '\0' }; + gdouble v0, v1; + + + v0 = _read_expr (parser); + c = _peek (parser); + if (c == '>' || c == '<') { + oper[0] = _next (parser); + c = _peek (parser); + if (c == '=') + oper[1] = _next (parser); + + + v1 = _read_expr (parser); + + if (g_strcmp0 (oper, "<") == 0) { + v0 = (v0 < v1) ? 1.0 : 0.0; + } else if (g_strcmp0 (oper, ">") == 0) { + v0 = (v0 > v1) ? 1.0 : 0.0; + } else if (g_strcmp0 (oper, "<=") == 0) { + v0 = (v0 <= v1) ? 1.0 : 0.0; + } else if (g_strcmp0 (oper, ">=") == 0) { + v0 = (v0 >= v1) ? 1.0 : 0.0; + } else { + _error (parser, "Unknown operation!"); + } + } + return v0; +} + +static gdouble +_read_boolean_equality (MathParser * parser) +{ + gchar c, oper[] = { '\0', '\0', '\0' }; + gdouble v0, v1; + + v0 = _read_boolean_comparison (parser); + c = _peek (parser); + if (c == '=' || c == '!') { + if (c == '!') { + if (_peek_n (parser, 1) == '=') { + oper[0] = _next (parser); + oper[1] = _next (parser); + } else { + return v0; + } + } else { + oper[0] = _next (parser); + c = _peek (parser); + if (c != '=') + _error (parser, "Expected a '=' for boolean '==' operator!"); + oper[1] = _next (parser); + } + v1 = _read_boolean_comparison (parser); + if (g_strcmp0 (oper, "==") == 0) { + v0 = (fabs (v0 - v1) < PARSER_BOOLEAN_EQUALITY_THRESHOLD) ? 1.0 : 0.0; + } else if (g_strcmp0 (oper, "!=") == 0) { + v0 = (fabs (v0 - v1) > PARSER_BOOLEAN_EQUALITY_THRESHOLD) ? 1.0 : 0.0; + } else { + _error (parser, "Unknown operation!"); + } + } + return v0; +} + +static gdouble +_read_boolean_and (MathParser * parser) +{ + gchar c; + gdouble v0, v1; + + v0 = _read_boolean_equality (parser); + + c = _peek (parser); + while (c == '&') { + _next (parser); + + c = _peek (parser); + if (c != '&') + _error (parser, "Expected '&' to follow '&' in logical and operation!"); + _next (parser); + + v1 = _read_boolean_equality (parser); + v0 = (fabs (v0) >= PARSER_BOOLEAN_EQUALITY_THRESHOLD + && fabs (v1) >= PARSER_BOOLEAN_EQUALITY_THRESHOLD) ? 1.0 : 0.0; + + c = _peek (parser); + } + + return v0; +} + +static gdouble +_read_boolean_or (MathParser * parser) +{ + gchar c; + gdouble v0, v1; + + v0 = _read_boolean_and (parser); + + c = _peek (parser); + while (c == '|') { + _next (parser); + c = _peek (parser); + if (c != '|') + _error (parser, "Expected '|' to follow '|' in logical or operation!"); + _next (parser); + v1 = _read_boolean_and (parser); + v0 = (fabs (v0) >= PARSER_BOOLEAN_EQUALITY_THRESHOLD + || fabs (v1) >= PARSER_BOOLEAN_EQUALITY_THRESHOLD) ? 1.0 : 0.0; + c = _peek (parser); + } + + return v0; +} + +static gboolean +_init (MathParser * parser, const gchar * str, + GstValidateParseVariableFunc variable_func, void *user_data) +{ + parser->str = str; + parser->len = strlen (str) + 1; + parser->pos = 0; + parser->error = NULL; + parser->user_data = user_data; + parser->variable_func = variable_func; + + return TRUE; +} + +static gdouble +_parse (MathParser * parser) +{ + gdouble result = 0.0; + + if (!setjmp (parser->err_jmp_buf)) { + result = _read_expr (parser); + if (parser->pos < parser->len - 1) { + _error (parser, + "Failed to reach end of input expression, likely malformed input"); + } else + return result; + } + + return -1.0; +} + +static gdouble +_read_argument (MathParser * parser) +{ + gchar c; + gdouble val; + + val = _read_expr (parser); + c = _peek (parser); + if (c == ',') + _next (parser); + + return val; +} + +static gdouble +_read_builtin (MathParser * parser) +{ + gdouble v0 = 0.0, v1 = 0.0; + gchar c, token[PARSER_MAX_TOKEN_SIZE]; + gint pos = 0; + + c = _peek (parser); + if (isalpha (c) || c == '_' || c == '$') { + while (isalpha (c) || isdigit (c) || c == '_' || c == '$') { + token[pos++] = _next (parser); + c = _peek (parser); + } + token[pos] = '\0'; + + if (_peek (parser) == '(') { + _next (parser); + if (g_strcmp0 (token, "min") == 0) { + v0 = _read_argument (parser); + v1 = _read_argument (parser); + v0 = MIN (v0, v1); + } else if (g_strcmp0 (token, "max") == 0) { + v0 = _read_argument (parser); + v1 = _read_argument (parser); + v0 = MAX (v0, v1); + } else { + gchar *tmp = + g_strdup_printf ("Tried to call unknown built-in function: %s", + token); + _error (parser, tmp); + g_free (tmp); + } + + if (_next (parser) != ')') + _error (parser, "Expected ')' in built-in call!"); + } else { + if (parser->variable_func != NULL + && parser->variable_func (token, &v1, parser->user_data)) { + v0 = v1; + } else { + gchar *err = + g_strdup_printf ("Could not look up value for variable %s!", token); + _error (parser, err); + g_free (err); + } + } + } else { + v0 = _read_double (parser); + } + + return v0; +} + +static gdouble +_read_parenthesis (MathParser * parser) +{ + gdouble val; + + if (_peek (parser) == '(') { + _next (parser); + val = _read_boolean_or (parser); + if (_peek (parser) != ')') + _error (parser, "Expected ')'!"); + _next (parser); + } else { + val = _read_builtin (parser); + } + + return val; +} + +static gdouble +_read_unary (MathParser * parser) +{ + gchar c; + gdouble v0 = 0.0; + + c = _peek (parser); + if (c == '!') { + _error (parser, "Expected '+' or '-' for unary expression, got '!'"); + } else if (c == '-') { + _next (parser); + v0 = -_read_parenthesis (parser); + } else if (c == '+') { + _next (parser); + v0 = _read_parenthesis (parser); + } else { + v0 = _read_parenthesis (parser); + } + return v0; +} + +static gdouble +_read_power (MathParser * parser) +{ + gdouble v0, v1 = 1.0, s = 1.0; + + v0 = _read_unary (parser); + + while (_peek (parser) == '^') { + _next (parser); + if (_peek (parser) == '-') { + _next (parser); + s = -1.0; + } + v1 = s * _read_power (parser); + v0 = pow (v0, v1); + } + + return v0; +} + +/** + * gst_validate_utils_parse_expression: (skip): + */ +gdouble +gst_validate_utils_parse_expression (const gchar * expr, + GstValidateParseVariableFunc variable_func, gpointer user_data, + gchar ** error) +{ + gdouble val; + MathParser parser; + gchar **spl = g_strsplit (expr, " ", -1); + gchar *expr_nospace = g_strjoinv ("", spl); + + _init (&parser, expr_nospace, variable_func, user_data); + val = _parse (&parser); + g_strfreev (spl); + g_free (expr_nospace); + + if (error) { + if (parser.error) + *error = g_strdup (parser.error); + else + *error = NULL; + } + return val; +} + +/** + * gst_validate_utils_flags_from_str: + * @type: The #GType of the flags we are trying to retrieve the flags from + * @str_flags: The string representation of the value + * + * Returns: The flags set in @str_flags + */ +guint +gst_validate_utils_flags_from_str (GType type, const gchar * str_flags) +{ + guint flags; + GValue value = G_VALUE_INIT; + g_value_init (&value, type); + + if (!gst_value_deserialize (&value, str_flags)) { + gst_validate_abort ("Invalid flags: %s", str_flags); + + return 0; + } + + flags = g_value_get_flags (&value); + g_value_unset (&value); + + return flags; +} + +/** + * gst_validate_utils_enum_from_str: + * @type: The #GType of the enum we are trying to retrieve the enum value from + * @str_enum: The string representation of the value + * @enum_value: (out): The value of the enum + * + * Returns: %TRUE on success %FALSE otherwise + */ +gboolean +gst_validate_utils_enum_from_str (GType type, const gchar * str_enum, + guint * enum_value) +{ + GValue value = G_VALUE_INIT; + g_value_init (&value, type); + + if (!gst_value_deserialize (&value, str_enum)) { + gst_validate_abort ("Invalid enum: %s", str_enum); + + return FALSE; + } + + *enum_value = g_value_get_enum (&value); + g_value_unset (&value); + + return TRUE; +} + + +static gchar * +skip_spaces (gchar * c) +{ + /* For some reason newlines are considered as space, we do not want that! */ + while (g_ascii_isspace (*c) && *c != '\n') + c++; + + return c; +} + +static void +setup_quarks (void) +{ + if (filename_quark) + return; + + filename_quark = g_quark_from_static_string ("__filename__"); + lineno_quark = g_quark_from_static_string ("__lineno__"); + debug_quark = g_quark_from_static_string ("__debug__"); +} + +gboolean +gst_validate_has_colored_output (void) +{ + return g_log_writer_supports_color (fileno (stdout)); +} + +/* Parse file that contains a list of GStructures */ +#define GST_STRUCT_LINE_CONTINUATION_CHARS ",{\\[<" +static GList * +_file_get_structures (GFile * file, gchar ** err, + GstValidateGetIncludePathsFunc get_include_paths_func) +{ + gsize size; + + GError *error = NULL; + gchar *content = NULL, *tmp; + gchar *filename = NULL; + gint lineno = 1, current_lineno; + GList *structures = NULL; + GString *errstr = NULL; + gchar *red = NULL, *bold = NULL; + const gchar *endcolor = ""; + + if (err) + errstr = g_string_new (NULL); + + if (gst_validate_has_colored_output ()) { + red = gst_debug_construct_term_color (GST_DEBUG_FG_RED); + bold = gst_debug_construct_term_color (GST_DEBUG_BOLD); + endcolor = "\033[0m"; + } else { + red = g_strdup (""); + bold = g_strdup (""); + } + + + filename = g_file_get_path (file); + /* TODO Handle GCancellable */ + if (!g_file_load_contents (file, NULL, &content, &size, NULL, &error)) { + if (errstr && !get_include_paths_func) + g_string_append_printf (errstr, + "\n%s%s:%s %sFailed to load content%s\n | %s", + bold, filename, endcolor, red, endcolor, error->message); + else + GST_WARNING ("Failed to load contents: %d %s", error->code, + error->message); + g_error_free (error); + goto failed; + } + if (g_strcmp0 (content, "") == 0) { + goto done; + } + + tmp = content; + while (*tmp) { + GString *l, *debug_line; + GstStructure *structure; + + tmp = skip_spaces (tmp); + + if (*tmp == '\n') { + tmp++; + lineno++; + continue; + } + + if (*tmp == '#') { + while (*tmp && *tmp != '\n') + tmp++; + if (*tmp) + tmp++; + lineno++; + continue; + } + + l = g_string_new (NULL); + debug_line = g_string_new (NULL); + current_lineno = lineno; + g_string_append_printf (debug_line, " %4d | ", lineno); + while (*tmp != '\n' && *tmp) { + gchar next; + + if (*tmp == '#') { + while (*tmp && *tmp != '\n') { + g_string_append_c (debug_line, *tmp); + tmp++; + } + tmp++; + g_string_append_printf (debug_line, "\n %4d | ", lineno + 1); + lineno++; + continue; + } + + next = *(tmp + 1); + if (next && (next == '\n' || next == '\r') + && strchr (GST_STRUCT_LINE_CONTINUATION_CHARS, *tmp)) { + g_string_append_c (debug_line, *tmp); + g_string_append_printf (debug_line, "\n %4d | ", lineno + 1); + if (*tmp != '\\') + g_string_append_c (l, *tmp); + + tmp++; + while (*tmp == '\n' || *tmp == '\r') + tmp++; + lineno++; + continue; + } + + g_string_append_c (debug_line, *tmp); + g_string_append_c (l, *tmp); + tmp += 1; + } + + /* Blank lines at EOF */ + if (!*l->str) { + g_string_free (l, TRUE); + g_string_free (debug_line, TRUE); + continue; + } + + structure = gst_structure_from_string (l->str, NULL); + if (structure == NULL) { + if (errstr) { + g_string_append_printf (errstr, + "\n%s%s:%d-%d:%s %sInvalid structure%s\n%s", + bold, filename, current_lineno, lineno, endcolor, + red, endcolor, debug_line->str); + + if (strchr (debug_line->str, '\n')) + g_string_append_printf (errstr, "\n > %s\n", l->str); + + g_string_append_c (errstr, '\n'); + } else { + g_string_free (l, TRUE); + g_string_free (debug_line, TRUE); + goto failed; + } + } else { + if (gst_structure_has_name (structure, "include")) { + gchar *included_err = NULL; + const gchar *location = + gst_structure_get_string (structure, "location"); + + if (location == NULL) { + if (errstr) { + g_string_append_printf (errstr, + "\n%s%s:%d-%d:%s %sMissing field 'location' in `include` structure%s\n%s", + bold, filename, current_lineno, lineno, endcolor, + red, endcolor, debug_line->str); + + if (strchr (debug_line->str, '\n')) + g_string_append_printf (errstr, "\n > %s\n", l->str); + + g_string_append_c (errstr, '\n'); + } else { + g_string_free (l, TRUE); + g_string_free (debug_line, TRUE); + goto failed; + } + } else { + gchar *included_path = NULL; + GFile *included = NULL; + GList *tmpstructures; + gchar **include_dirs = NULL; + + if (!get_include_paths_func + && g_str_has_suffix (location, GST_VALIDATE_SCENARIO_SUFFIX)) { + GST_INFO + ("Trying to include a scenario, take into account scenario include dir"); + + get_include_paths_func = (GstValidateGetIncludePathsFunc) + gst_validate_scenario_get_include_paths; + } + + if (get_include_paths_func) + include_dirs = get_include_paths_func (filename); + + if (!include_dirs) { + GFile *dir = g_file_get_parent (file); + included = g_file_resolve_relative_path (dir, location); + + g_object_unref (dir); + } else { + gint i; + + for (i = 0; include_dirs[i]; i++) { + g_clear_object (&included); + included = + g_file_new_build_filename (include_dirs[i], location, NULL); + if (g_file_query_exists (included, NULL)) + break; + + /* We let the last attempt fail and report an error in the + * including code path */ + } + } + + included_path = g_file_get_path (included); + GST_INFO ("%s including %s", filename, included_path); + g_free (included_path); + + tmpstructures = _file_get_structures (included, &included_err, + get_include_paths_func); + if (included_err) { + if (errstr) { + gchar *c; + + g_string_append_printf (errstr, + "\n%s%s:%d-%d:%s %sError including %s%s\n%s", + bold, filename, current_lineno, lineno, endcolor, + red, location, endcolor, debug_line->str); + + if (strchr (debug_line->str, '\n')) + g_string_append_printf (errstr, "\n > %s\n", l->str); + + for (c = included_err; *c != '\0' && *(c + 1) != '\0'; c++) { + g_string_append_c (errstr, *c); + if (*c == '\n') + g_string_append (errstr, " | "); + } + g_free (included_err); + } else { + g_free (included_err); + g_string_free (l, TRUE); + g_string_free (debug_line, TRUE); + g_object_unref (included); + goto failed; + } + } + g_object_unref (included); + structures = g_list_concat (structures, tmpstructures); + } + gst_structure_free (structure); + } else { + setup_quarks (); + gst_structure_id_set (structure, + lineno_quark, G_TYPE_INT, current_lineno, + filename_quark, G_TYPE_STRING, filename, + debug_quark, G_TYPE_STRING, debug_line->str, NULL); + structures = g_list_append (structures, structure); + } + } + + g_string_free (l, TRUE); + g_string_free (debug_line, TRUE); + lineno++; + if (*tmp) + tmp++; + } + +done: + if (err) + *err = g_string_free (errstr, errstr->len ? FALSE : TRUE); + g_free (content); + g_free (filename); + g_free (bold); + g_free (red); + return structures; + +failed: + if (structures) + g_list_free_full (structures, (GDestroyNotify) gst_structure_free); + structures = NULL; + + goto done; +} + +static GList * +_get_structures (const gchar * structured_file, gchar ** file_path, + GstValidateGetIncludePathsFunc get_include_paths_func, gchar ** err) +{ + GFile *file = NULL; + GList *structs = NULL; + + GST_DEBUG ("Trying to load %s", structured_file); + if ((file = g_file_new_for_path (structured_file)) == NULL) { + GST_WARNING ("%s wrong uri", structured_file); + if (err) + *err = g_strdup_printf ("%s wrong uri", structured_file); + return NULL; + } + + if (file_path) + *file_path = g_file_get_path (file); + + structs = _file_get_structures (file, err, get_include_paths_func); + + g_object_unref (file); + + return structs; +} + +/** + * gst_validate_utils_structs_parse_from_filename: (skip): + */ +GList * +gst_validate_utils_structs_parse_from_filename (const gchar * structured_file, + GstValidateGetIncludePathsFunc get_include_paths_func, gchar ** file_path) +{ + GList *res; + gchar *err = NULL; + + res = + _get_structures (structured_file, file_path, get_include_paths_func, + &err); + + if (err) + gst_validate_abort ("Could not get structures from %s:\n%s\n", + structured_file, err); + + return res; +} + +/** + * gst_validate_structs_parse_from_gfile: (skip): + */ +GList * +gst_validate_structs_parse_from_gfile (GFile * structured_file, + GstValidateGetIncludePathsFunc get_include_paths_func) +{ + gchar *err = NULL; + GList *res; + + res = _file_get_structures (structured_file, &err, get_include_paths_func); + if (err) + gst_validate_abort ("Could not get structures from %s:\n%s\n", + g_file_get_uri (structured_file), err); + + return res; +} + +gboolean +gst_validate_element_has_klass (GstElement * element, const gchar * klass) +{ + const gchar *tmp; + gchar **a, **b; + gboolean result = FALSE; + guint i; + + tmp = gst_element_class_get_metadata (GST_ELEMENT_GET_CLASS (element), + GST_ELEMENT_METADATA_KLASS); + + a = g_strsplit (klass, "/", -1); + b = g_strsplit (tmp, "/", -1); + + /* All the elements in 'a' have to be in 'b' */ + for (i = 0; a[i] != NULL; i++) + if (!g_strv_contains ((const char *const *) b, a[i])) + goto done; + result = TRUE; + +done: + g_strfreev (a); + g_strfreev (b); + return result; +} + +static gboolean +gst_validate_convert_string_to_clocktime (const gchar * strtime, + GstClockTime * retval) +{ + guint h, m, s, ns; + gchar *other = g_strdup (strtime); + gboolean res = TRUE; + + if (sscanf (strtime, "%" GST_TIME_FORMAT "%s", &h, &m, &s, &ns, other) < 4) { + GST_DEBUG ("Can not sscanf %s", strtime); + + goto fail; + } + + *retval = (h * 3600 + m * 60 + s) * GST_SECOND + ns; + +done: + g_free (other); + return res; + +fail: + res = FALSE; + goto done; +} + +/** + * gst_validate_utils_get_clocktime: + * @structure: A #GstStructure to retrieve @name as a GstClockTime. + * @name: The name of the field containing a #GstClockTime + * @retval: (out): The clocktime contained in @structure + * + * Get @name from @structure as a #GstClockTime, it handles various types + * for the value, if it is a double, it considers the value to be in second + * it can be a gint, gint64 a guint, a gint64. + * + * Return: %TRUE in case of success, %FALSE otherwise. + */ +gboolean +gst_validate_utils_get_clocktime (GstStructure * structure, const gchar * name, + GstClockTime * retval) +{ + gdouble val; + const GValue *gvalue = gst_structure_get_value (structure, name); + + *retval = GST_CLOCK_TIME_NONE; + if (gvalue == NULL) { + return FALSE; + } + + if (G_VALUE_TYPE (gvalue) == GST_TYPE_CLOCK_TIME) { + *retval = g_value_get_uint64 (gvalue); + + return TRUE; + } + + if (G_VALUE_TYPE (gvalue) == G_TYPE_UINT64) { + *retval = g_value_get_uint64 (gvalue); + + return TRUE; + } + + if (G_VALUE_TYPE (gvalue) == G_TYPE_UINT) { + *retval = (GstClockTime) g_value_get_uint (gvalue); + + return TRUE; + } + + if (G_VALUE_TYPE (gvalue) == G_TYPE_INT) { + *retval = (GstClockTime) g_value_get_int (gvalue); + + return TRUE; + } + + if (G_VALUE_TYPE (gvalue) == G_TYPE_INT64) { + *retval = (GstClockTime) g_value_get_int64 (gvalue); + + return TRUE; + } + + if (G_VALUE_TYPE (gvalue) == G_TYPE_STRING) { + return + gst_validate_convert_string_to_clocktime (g_value_get_string (gvalue), + retval); + } + + + if (!gst_structure_get_double (structure, name, &val)) { + return FALSE; + } + + if (val == -1.0) + *retval = GST_CLOCK_TIME_NONE; + else { + *retval = val * GST_SECOND; + *retval = GST_ROUND_UP_4 (*retval); + } + + return TRUE; +} + +GstValidateActionReturn +gst_validate_object_set_property (GstValidateReporter * reporter, + GObject * object, const gchar * property, + const GValue * value, gboolean optional) +{ + GParamSpec *paramspec; + GObjectClass *klass = G_OBJECT_GET_CLASS (object); + GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK; + GValue cvalue = G_VALUE_INIT, nvalue = G_VALUE_INIT; + + paramspec = g_object_class_find_property (klass, property); + if (paramspec == NULL) { + if (optional) + return TRUE; + GST_ERROR ("Target doesn't have property %s", property); + return FALSE; + } + + g_value_init (&cvalue, paramspec->value_type); + if (paramspec->value_type != G_VALUE_TYPE (value) && + (G_VALUE_TYPE (value) == G_TYPE_STRING)) { + if (!gst_value_deserialize (&cvalue, g_value_get_string (value))) { + GST_VALIDATE_REPORT (reporter, SCENARIO_ACTION_EXECUTION_ERROR, + "Could not set %" GST_PTR_FORMAT "::%s as value %s" + " could not be deserialize to %s", object, property, + g_value_get_string (value), G_PARAM_SPEC_TYPE_NAME (paramspec)); + + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + } else { + if (!g_value_transform (value, &cvalue)) { + GST_VALIDATE_REPORT (reporter, SCENARIO_ACTION_EXECUTION_ERROR, + "Could not set %" GST_PTR_FORMAT " property %s to type %s" + " (wanted type %s)", object, property, G_VALUE_TYPE_NAME (value), + G_PARAM_SPEC_TYPE_NAME (paramspec)); + + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + + } + + g_object_set_property (object, property, &cvalue); + + g_value_init (&nvalue, paramspec->value_type); + g_object_get_property (object, property, &nvalue); + + if (gst_value_compare (&cvalue, &nvalue) != GST_VALUE_EQUAL) { + gchar *nvalstr = gst_value_serialize (&nvalue); + gchar *cvalstr = gst_value_serialize (&cvalue); + GST_VALIDATE_REPORT (reporter, SCENARIO_ACTION_EXECUTION_ERROR, + "Setting value %" GST_PTR_FORMAT "::%s failed, expected value: %s" + " value after setting %s", object, property, cvalstr, nvalstr); + + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + g_free (nvalstr); + g_free (cvalstr); + } + + g_value_reset (&cvalue); + g_value_reset (&nvalue); + return res; +} + +#ifdef G_OS_UNIX +static void +fault_restore (void) +{ + struct sigaction action; + + memset (&action, 0, sizeof (action)); + action.sa_handler = SIG_DFL; + + sigaction (SIGSEGV, &action, NULL); + sigaction (SIGQUIT, &action, NULL); +} + +static void +fault_spin (void) +{ + int spinning = TRUE; + + g_on_error_stack_trace ("GstValidate"); + + wait (NULL); + + g_printerr ("Please run 'gdb %d' to " + "continue debugging, Ctrl-C to quit, or Ctrl-\\ to dump core.\n", + (gint) getpid ()); + + while (spinning) + g_usleep (1000000); +} + +static void +fault_handler_sighandler (int signum) +{ + fault_restore (); + + /* printf is used instead of g_print(), since it's less likely to + * deadlock */ + switch (signum) { + case SIGSEGV: + g_printerr ("\n"); + break; + case SIGQUIT: + gst_validate_printf (NULL, "\n"); + break; + default: + g_printerr ("\n", signum); + break; + } + + fault_spin (); +} + +static void +fault_setup (void) +{ + struct sigaction action; + + memset (&action, 0, sizeof (action)); + action.sa_handler = fault_handler_sighandler; + + sigaction (SIGSEGV, &action, NULL); + sigaction (SIGQUIT, &action, NULL); +} +#endif /* G_OS_UNIX */ + +void +gst_validate_spin_on_fault_signals (void) +{ +#ifdef G_OS_UNIX + fault_setup (); +#endif +} + +/** + * gst_validate_element_matches_target: + * @element: a #GstElement to check + * @s: a #GstStructure to use for matching + * + * Check if @element matches one of the 'target-element-name', + * 'target-element-klass' or 'target-element-factory-name' defined in @s. + * + * Return: %TRUE if it matches, %FALSE otherwise or if @s doesn't contain any + * target-element field. + */ +gboolean +gst_validate_element_matches_target (GstElement * element, GstStructure * s) +{ + const gchar *tmp; + + tmp = gst_structure_get_string (s, "target-element-name"); + if (tmp != NULL && !g_strcmp0 (tmp, GST_ELEMENT_NAME (element))) + return TRUE; + + tmp = gst_structure_get_string (s, "target-element-klass"); + if (tmp != NULL && gst_validate_element_has_klass (element, tmp)) + return TRUE; + + tmp = gst_structure_get_string (s, "target-element-factory-name"); + if (tmp != NULL && gst_element_get_factory (element) + && !g_strcmp0 (GST_OBJECT_NAME (gst_element_get_factory (element)), tmp)) + return TRUE; + + return FALSE; +} + +static gchar * +gst_structure_get_value_as_string (GstStructure * structure, + const gchar * field) +{ + const GValue *val = gst_structure_get_value (structure, field); + + if (!val) + return NULL; + + if (G_VALUE_HOLDS_STRING (val)) + return g_value_dup_string (val); + + return gst_value_serialize (val); +} + +gchar * +gst_validate_replace_variables_in_string (gpointer source, + GstStructure * local_vars, const gchar * in_string, + GstValidateStructureResolveVariablesFlags flags) +{ + gint varname_len; + GMatchInfo *match_info = NULL; + gchar *tmpstring, *string = g_strdup (in_string); + + if (!_variables_regex) + _variables_regex = g_regex_new ("\\$\\((\\w+)\\)", 0, 0, NULL); + + gst_validate_set_globals (NULL); + + while (g_regex_match (_variables_regex, string, 0, &match_info)) { + gchar *var_value = NULL; + + if (g_match_info_matches (match_info)) { + GRegex *replace_regex; + gchar *tmp, *varname, *pvarname = g_match_info_fetch (match_info, 0); + + varname_len = strlen (pvarname); + varname = g_malloc (sizeof (gchar) * (varname_len - 2)); + strncpy (varname, &pvarname[2], varname_len - 3); + varname[varname_len - 3] = '\0'; + + if (local_vars && gst_structure_has_field_typed (local_vars, varname, + G_TYPE_DOUBLE)) { + var_value = g_strdup (varname); + } else { + if (local_vars) + var_value = gst_structure_get_value_as_string (local_vars, varname); + + if (!var_value + && !(flags & GST_VALIDATE_STRUCTURE_RESOLVE_VARIABLES_LOCAL_ONLY)) + var_value = gst_structure_get_value_as_string (global_vars, varname); + } + + if (!var_value) { + if (!(flags & GST_VALIDATE_STRUCTURE_RESOLVE_VARIABLES_NO_FAILURE)) { + gst_validate_error_structure (source, + "Trying to use undefined variable `%s`.\n" + " Available vars:\n" + " - locals%s\n" + " - globals%s\n", + varname, gst_structure_to_string (local_vars), + (flags & GST_VALIDATE_STRUCTURE_RESOLVE_VARIABLES_LOCAL_ONLY) ? + ": unused" : gst_structure_to_string (global_vars)); + } + + return NULL; + } + + tmp = g_strdup_printf ("\\$\\(%s\\)", varname); + replace_regex = g_regex_new (tmp, 0, 0, NULL); + g_free (tmp); + tmpstring = string; + string = + g_regex_replace_literal (replace_regex, string, -1, 0, var_value, 0, + NULL); + + GST_INFO ("Setting variable %s to %s", varname, var_value); + g_free (tmpstring); + g_free (var_value); + g_regex_unref (replace_regex); + g_free (pvarname); + g_free (varname); + } + g_clear_pointer (&match_info, g_match_info_free); + } + + if (match_info) + g_match_info_free (match_info); + + return string; +} + +typedef struct +{ + gpointer source; + GstStructure *local_vars; + GstValidateStructureResolveVariablesFlags flags; +} ReplaceData; + +static void +_resolve_expression (gpointer source, GValue * value) +{ + gdouble new_value; + GMatchInfo *match_info = NULL; + gchar *error = NULL; + gchar *v, *expr, *tmp; + + g_assert (G_VALUE_HOLDS_STRING (value)); + + tmp = expr = v = g_value_dup_string (value); + tmp = skip_spaces (tmp); + expr = strstr (v, "expr("); + if (expr != tmp) + goto done; + + expr = &expr[5]; + tmp = &expr[strlen (expr) - 1]; + while (g_ascii_isspace (*tmp) && tmp != expr) + tmp--; + + if (tmp == expr || *tmp != ')') + goto done; + + *tmp = '\0'; + new_value = gst_validate_utils_parse_expression (expr, NULL, NULL, &error); + if (error) + gst_validate_error_structure (source, "Could not parse expression %s: %s", + expr, error); + g_value_unset (value); + g_value_init (value, G_TYPE_DOUBLE); + g_value_set_double (value, new_value); + +done: + g_free (v); + g_clear_pointer (&match_info, g_match_info_free); +} + +static gboolean +_structure_set_variables (GQuark field_id, GValue * value, ReplaceData * data) +{ + if (field_id == filename_quark || field_id == debug_quark + || field_id == debug_quark) + return TRUE; + + if (GST_VALUE_HOLDS_LIST (value)) { + gint i; + + for (i = 0; i < gst_value_list_get_size (value); i++) + _structure_set_variables (0, (GValue *) gst_value_list_get_value (value, + i), data); + + return TRUE; + } + + if (!G_VALUE_HOLDS_STRING (value)) + return TRUE; + + if (!_variables_regex) + _variables_regex = g_regex_new ("\\$\\((\\w+)\\)", 0, 0, NULL); + + /* Don't replace string contents unless really needed */ + if (g_regex_match (_variables_regex, g_value_get_string (value), 0, NULL)) { + gchar *str = gst_validate_replace_variables_in_string (data->source, + data->local_vars, + g_value_get_string (value), data->flags); + if (str) { + g_value_set_string (value, str); + g_free (str); + } + } + + if (!(data->flags & GST_VALIDATE_STRUCTURE_RESOLVE_VARIABLES_NO_EXPRESSION)) + _resolve_expression (data->source, value); + + return TRUE; +} + +void +gst_validate_structure_resolve_variables (gpointer source, + GstStructure * structure, GstStructure * local_variables, + GstValidateStructureResolveVariablesFlags flags) +{ + ReplaceData d = { source ? source : structure, local_variables, flags }; + + gst_structure_filter_and_map_in_place (structure, + (GstStructureFilterMapFunc) _structure_set_variables, &d); +} + +static gboolean +_set_vars_func (GQuark field_id, const GValue * value, GstStructure * vars) +{ + gst_structure_id_set_value (vars, field_id, value); + + return TRUE; +} + +static void +structure_set_string_literal (GstStructure * structure, const gchar * fieldname, + const gchar * str) +{ + gint i; + GString *escaped = g_string_sized_new (strlen (str) + 1); + + for (i = 0; str[i] != '\0'; i++) { + g_string_append_c (escaped, str[i]); + if (str[i] == '\\') + g_string_append_c (escaped, '\\'); + } + gst_structure_set (structure, fieldname, G_TYPE_STRING, escaped->str, NULL); + g_string_free (escaped, TRUE); +} + +void +gst_validate_set_globals (GstStructure * structure) +{ + if (!global_vars) { + const gchar *logsdir = g_getenv ("GST_VALIDATE_LOGSDIR"); + + if (!logsdir) + logsdir = g_get_tmp_dir (); + + global_vars = gst_structure_new_empty ("vars"); + structure_set_string_literal (global_vars, "TMPDIR", g_get_tmp_dir ()); + structure_set_string_literal (global_vars, "LOGSDIR", logsdir); + structure_set_string_literal (global_vars, "tmpdir", g_get_tmp_dir ()); + structure_set_string_literal (global_vars, "logsdir", logsdir); + } + + if (!structure) + return; + + gst_structure_foreach (structure, + (GstStructureForeachFunc) _set_vars_func, global_vars); +} + +/** + * gst_validate_utils_get_strv: + * @str: A GstStructure + * @fieldname: A fieldname containing a GstValueList or is not defined + * + * Returns: (transfer full): An array of strings from the GstValueList defined in @fieldname + */ +gchar ** +gst_validate_utils_get_strv (GstStructure * str, const gchar * fieldname) +{ + const GValue *value; + gchar **parsed_list; + guint i, size; + + value = gst_structure_get_value (str, fieldname); + if (!value) + return NULL; + + if (G_VALUE_HOLDS_STRING (value)) { + parsed_list = g_new0 (gchar *, 2); + parsed_list[0] = g_value_dup_string (value); + + return parsed_list; + } + + if (!GST_VALUE_HOLDS_LIST (value)) { + g_error + ("%s must have type list of string (or a string), e.g. %s={ val1, val2 }, got: \"%s\" in %s", + fieldname, fieldname, gst_value_serialize (value), + gst_structure_to_string (str)); + return NULL; + } + + size = gst_value_list_get_size (value); + parsed_list = g_malloc_n (size + 1, sizeof (gchar *)); + for (i = 0; i < size; i++) + parsed_list[i] = g_value_dup_string (gst_value_list_get_value (value, i)); + parsed_list[i] = NULL; + return parsed_list; +} + +static void +strip_ext (char *fname) +{ + char *end = fname + strlen (fname); + + while (end > fname && *end != '.') + --end; + + if (end > fname) + *end = '\0'; +} + +/* NOTE: vars == NULL implies that we are working on a testfile and the variables + * will be set globally */ +void +gst_validate_structure_set_variables_from_struct_file (GstStructure * vars, + const gchar * struct_file) +{ + gchar *config_dir; + gchar *config_fname; + gchar *config_name; + gchar *t, *config_name_dir; + gchar *validateflow, *expectations_dir, *actual_result_dir; + const gchar *logdir; + gboolean local = ! !vars; + + if (!struct_file) + return; + + + if (!vars) + vars = global_vars; + + config_dir = g_path_get_dirname (struct_file); + config_fname = g_path_get_basename (struct_file); + config_name = g_strdup (config_fname); + + gst_validate_set_globals (NULL); + logdir = gst_structure_get_string (global_vars, "logsdir"); + g_assert (logdir); + + strip_ext (config_name); + config_name_dir = g_strdup (config_name); + for (t = config_name_dir; *t != '\0'; t++) { + if (*t == '.') + *t = '/'; + } + + expectations_dir = + g_build_path ("/", config_dir, config_name, "flow-expectations", NULL); + actual_result_dir = g_build_path ("/", logdir, config_name_dir, NULL); + validateflow = + g_strdup_printf + ("validateflow, expectations-dir=\"%s\", actual-results-dir=\"%s\"", + expectations_dir, actual_result_dir); + + structure_set_string_literal (vars, "gst_api_version", GST_API_VERSION); + structure_set_string_literal (vars, !local ? "test_dir" : "CONFIG_DIR", + config_dir); + structure_set_string_literal (vars, !local ? "test_name" : "CONFIG_NAME", + config_name); + structure_set_string_literal (vars, + !local ? "test_name_dir" : "CONFIG_NAME_DIR", config_name_dir); + structure_set_string_literal (vars, !local ? "test_path" : "CONFIG_PATH", + struct_file); + structure_set_string_literal (vars, "validateflow", validateflow); + + g_free (config_dir); + g_free (config_name_dir); + g_free (config_fname); + g_free (config_name); + g_free (validateflow); + g_free (actual_result_dir); + g_free (expectations_dir); +} + +void +gst_validate_set_test_file_globals (GstStructure * meta, const gchar * testfile, + gboolean use_fakesinks) +{ + gboolean needs_sync = FALSE; + const gchar *videosink, *audiosink; + + if (!use_fakesinks) { + videosink = "autovideosink"; + audiosink = "autoaudiosink"; + } else if (gst_structure_get_boolean (meta, "need-clock-sync", &needs_sync) + && needs_sync) { + videosink = "fakevideosink qos=true max-lateness=20000000"; + audiosink = "fakesink sync=true"; + } else { + videosink = "fakevideosink sync=false"; + audiosink = "fakesink"; + } + + gst_structure_set (global_vars, + "videosink", G_TYPE_STRING, videosink, + "audiosink", G_TYPE_STRING, audiosink, NULL); +} + +gboolean +gst_validate_fail_on_missing_plugin (void) +{ + + GList *config; + + for (config = gst_validate_plugin_get_config (NULL); config; + config = config->next) { + gboolean fail_on_missing_plugin; + + if (gst_structure_get_boolean (config->data, + "fail-on-missing-plugin", &fail_on_missing_plugin)) + return fail_on_missing_plugin; + } + return FALSE; +} diff --git a/validate/gst/validate/gst-validate-utils.h b/validate/gst/validate/gst-validate-utils.h new file mode 100644 index 0000000000..2589f18f15 --- /dev/null +++ b/validate/gst/validate/gst-validate-utils.h @@ -0,0 +1,93 @@ +/* GStreamer + * + * Copyright (C) 2013 Thibault Saunier + * + * gst-validate-utils.h - Some utility functions + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef EXPRESSION_PARSER_H +#define EXPRESSION_PARSER_H + +#include +#include +#include +#include +#include +#include "gst-validate-scenario.h" +#include "gst-validate-reporter.h" + +typedef int (*GstValidateParseVariableFunc) (const gchar *name, + double *value, gpointer user_data); + +typedef gchar** (*GstValidateGetIncludePathsFunc)(const gchar* includer_file); + +GST_VALIDATE_API +gdouble gst_validate_utils_parse_expression (const gchar *expr, + GstValidateParseVariableFunc variable_func, + gpointer user_data, + gchar **error); +GST_VALIDATE_API +guint gst_validate_utils_flags_from_str (GType type, const gchar * str_flags); +GST_VALIDATE_API +gboolean gst_validate_utils_enum_from_str (GType type, + const gchar * str_enum, + guint * enum_value); +GST_VALIDATE_API +gchar ** gst_validate_utils_get_strv (GstStructure *str, const gchar *fieldname); + +GST_VALIDATE_API +GList * gst_validate_utils_structs_parse_from_filename (const gchar * scenario_file, + GstValidateGetIncludePathsFunc get_include_paths_func, + gchar **file_path); +GST_VALIDATE_API +GstStructure * gst_validate_utils_test_file_get_meta (const gchar * testfile, gboolean use_fakesinks); + +GST_VALIDATE_API +GList * gst_validate_structs_parse_from_gfile (GFile * scenario_file, + GstValidateGetIncludePathsFunc get_include_paths_func); + +GST_VALIDATE_API +gboolean gst_validate_element_has_klass (GstElement * element, const gchar * klass); +GST_VALIDATE_API +gboolean gst_validate_utils_get_clocktime (GstStructure *structure, const gchar * name, + GstClockTime * retval); + +GST_VALIDATE_API +GstValidateActionReturn gst_validate_object_set_property (GstValidateReporter * reporter, + GObject * object, + const gchar * property, + const GValue * value, + gboolean optional); + +GST_VALIDATE_API +void gst_validate_spin_on_fault_signals (void); + +GST_VALIDATE_API +gboolean gst_validate_element_matches_target (GstElement * element, GstStructure * s); +gchar * gst_validate_replace_variables_in_string (gpointer incom, GstStructure * local_vars, const gchar * in_string, + GstValidateStructureResolveVariablesFlags flags); +GST_VALIDATE_API +void gst_validate_structure_resolve_variables (gpointer source, GstStructure *structure, GstStructure *local_variables, + GstValidateStructureResolveVariablesFlags flags); +void gst_validate_structure_set_variables_from_struct_file(GstStructure* vars, const gchar* struct_file); +void gst_validate_set_globals(GstStructure* structure); +GST_VALIDATE_API +gboolean gst_validate_fail_on_missing_plugin(void); +GST_VALIDATE_API gboolean gst_validate_has_colored_output(void); + +#endif diff --git a/validate/gst/validate/media-descriptor-parser.c b/validate/gst/validate/media-descriptor-parser.c new file mode 100644 index 0000000000..6733d4e42c --- /dev/null +++ b/validate/gst/validate/media-descriptor-parser.c @@ -0,0 +1,577 @@ +/* Gstreamer + * + * Copyright (c) 2012, Collabora Ltd. + * Author: Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "media-descriptor-parser.h" +#include + +struct _GstValidateMediaDescriptorParserPrivate +{ + gchar *xmlpath; + + gboolean in_stream; + gchar *xmlcontent; + GMarkupParseContext *parsecontext; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GstValidateMediaDescriptorParser, + gst_validate_media_descriptor_parser, GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR); + +enum +{ + PROP_0, + PROP_PATH, + N_PROPERTIES +}; + +/* Private methods and callbacks */ +static gint +compare_frames (GstValidateMediaFrameNode * frm, + GstValidateMediaFrameNode * frm1) +{ + if (frm->id < frm1->id) + return -1; + + else if (frm->id == frm1->id) + return 0; + + return 1; +} + +static void + deserialize_filenode + (GstValidateMediaFileNode * + filenode, const gchar ** names, const gchar ** values) +{ + gint i; + for (i = 0; names[i] != NULL; i++) { + if (g_strcmp0 (names[i], "uri") == 0) + filenode->uri = g_strdup (values[i]); + else if (g_strcmp0 (names[i], "id") == 0) + filenode->id = g_ascii_strtoull (values[i], NULL, 0); + else if (g_strcmp0 (names[i], "frame-detection") == 0) + filenode->frame_detection = g_ascii_strtoull (values[i], NULL, 0); + else if (g_strcmp0 (names[i], "duration") == 0) + filenode->duration = g_ascii_strtoull (values[i], NULL, 0); + else if (g_strcmp0 (names[i], "seekable") == 0) + filenode->seekable = (g_strcmp0 (values[i], "true") == 0); + } +} + +static GstValidateMediaStreamNode * +deserialize_streamnode (const gchar ** names, const gchar ** values) +{ + gint i; + GstValidateMediaStreamNode + * streamnode = g_slice_new0 (GstValidateMediaStreamNode); + + for (i = 0; names[i] != NULL; i++) { + if (g_strcmp0 (names[i], "id") == 0) + streamnode->id = g_strdup (values[i]); + else if (g_strcmp0 (names[i], "caps") == 0) + streamnode->caps = gst_caps_from_string (values[i]); + else if (g_strcmp0 (names[i], "padname") == 0) + streamnode->padname = g_strdup (values[i]); + } + + + return streamnode; +} + +static GstValidateSegmentNode * +deserialize_segmentnode (const gchar ** names, const gchar ** values) +{ + gint i; + GstValidateSegmentNode *node = g_slice_new0 (GstValidateSegmentNode); + + for (i = 0; names[i] != NULL; i++) { + if (!g_strcmp0 (names[i], "next-frame-id")) + node->next_frame_id = g_ascii_strtoull (values[i], NULL, 0); + else if (!g_strcmp0 (names[i], "flags")) + node->segment.flags = g_ascii_strtoull (values[i], NULL, 0); + else if (!g_strcmp0 (names[i], "rate")) + node->segment.rate = g_ascii_strtod (values[i], NULL); + else if (!g_strcmp0 (names[i], "applied-rate")) + node->segment.applied_rate = g_ascii_strtod (values[i], NULL); + else if (!g_strcmp0 (names[i], "format")) + node->segment.format = g_ascii_strtoull (values[i], NULL, 0); + else if (!g_strcmp0 (names[i], "base")) + node->segment.base = g_ascii_strtoull (values[i], NULL, 0); + else if (!g_strcmp0 (names[i], "offset")) + node->segment.offset = g_ascii_strtoull (values[i], NULL, 0); + else if (!g_strcmp0 (names[i], "start")) + node->segment.start = g_ascii_strtoull (values[i], NULL, 0); + else if (!g_strcmp0 (names[i], "stop")) + node->segment.stop = g_ascii_strtoull (values[i], NULL, 0); + else if (!g_strcmp0 (names[i], "time")) + node->segment.time = g_ascii_strtoull (values[i], NULL, 0); + else if (!g_strcmp0 (names[i], "position")) + node->segment.position = g_ascii_strtoull (values[i], NULL, 0); + else if (!g_strcmp0 (names[i], "duration")) + node->segment.duration = g_ascii_strtoull (values[i], NULL, 0); + } + + return node; +} + +static GstValidateMediaTagsNode * +deserialize_tagsnode (const gchar ** names, const gchar ** values) +{ + GstValidateMediaTagsNode *tagsnode = g_slice_new0 (GstValidateMediaTagsNode); + + return tagsnode; +} + +static GstValidateMediaTagNode * +deserialize_tagnode (const gchar ** names, const gchar ** values) +{ + gint i; + GstValidateMediaTagNode *tagnode = g_slice_new0 (GstValidateMediaTagNode); + + for (i = 0; names[i] != NULL; i++) { + if (g_strcmp0 (names[i], "content") == 0) + tagnode->taglist = gst_tag_list_new_from_string (values[i]); + } + + return tagnode; +} + +static GstValidateMediaFrameNode * +deserialize_framenode (const gchar ** names, const gchar ** values) +{ + gint i; + + GstValidateMediaFrameNode *framenode = + g_slice_new0 (GstValidateMediaFrameNode); + +/* *INDENT-OFF* */ +#define IF_SET_UINT64_FIELD(name,fieldname) \ + if (g_strcmp0 (names[i], name) == 0) { \ + if (g_strcmp0 (values[i], "unknown") == 0) \ + framenode->fieldname = GST_VALIDATE_UNKNOWN_UINT64; \ + else\ + framenode->fieldname = g_ascii_strtoull (values[i], NULL, 0); \ + } + + for (i = 0; names[i] != NULL; i++) { + IF_SET_UINT64_FIELD ("id", id) + else IF_SET_UINT64_FIELD ("offset", offset) + else IF_SET_UINT64_FIELD ("offset-end", offset_end) + else IF_SET_UINT64_FIELD ("duration", duration) + else IF_SET_UINT64_FIELD ("pts", pts) + else IF_SET_UINT64_FIELD ("dts", dts) + else IF_SET_UINT64_FIELD ("running-time", running_time) + else if (g_strcmp0 (names[i], "checksum") == 0) + framenode->checksum = g_strdup (values[i]); + else if (g_strcmp0 (names[i], "is-keyframe") == 0) { + if (!g_ascii_strcasecmp (values[i], "true")) + framenode->is_keyframe = TRUE; + else if (!g_ascii_strcasecmp (values[i], "unknown")) + framenode->is_keyframe = GST_VALIDATE_UNKNOWN_BOOL; + else + framenode->is_keyframe = FALSE; + } + } +/* *INDENT-ON* */ + + framenode->buf = gst_buffer_new_wrapped (framenode->checksum, + strlen (framenode->checksum) + 1); + + GST_BUFFER_OFFSET (framenode->buf) = framenode->offset; + GST_BUFFER_OFFSET_END (framenode->buf) = framenode->offset_end; + GST_BUFFER_DURATION (framenode->buf) = framenode->duration; + GST_BUFFER_PTS (framenode->buf) = framenode->pts; + GST_BUFFER_DTS (framenode->buf) = framenode->dts; + + if (framenode->is_keyframe) { + GST_BUFFER_FLAG_UNSET (framenode->buf, GST_BUFFER_FLAG_DELTA_UNIT); + } else { + GST_BUFFER_FLAG_SET (framenode->buf, GST_BUFFER_FLAG_DELTA_UNIT); + } + + return framenode; +} + + +static void +on_end_element_cb (GMarkupParseContext * context, + const gchar * element_name, gpointer user_data, GError ** error) +{ + GstValidateMediaDescriptorParserPrivate *priv = + GST_VALIDATE_MEDIA_DESCRIPTOR_PARSER (user_data)->priv; + + if (g_strcmp0 (element_name, "stream") == 0) { + priv->in_stream = FALSE; + } +} + +static void +on_start_element_cb (GMarkupParseContext * context, + const gchar * element_name, const gchar ** attribute_names, + const gchar ** attribute_values, gpointer user_data, GError ** error) +{ + GstValidateMediaFileNode + * filenode = GST_VALIDATE_MEDIA_DESCRIPTOR (user_data)->filenode; + + GstValidateMediaDescriptorParserPrivate *priv = + GST_VALIDATE_MEDIA_DESCRIPTOR_PARSER (user_data)->priv; + + if (g_strcmp0 (element_name, "file") == 0) { + deserialize_filenode (filenode, attribute_names, attribute_values); + } else if (g_strcmp0 (element_name, "stream") == 0) { + GstValidateMediaStreamNode + * node = deserialize_streamnode (attribute_names, attribute_values); + priv->in_stream = TRUE; + filenode->streams = g_list_prepend (filenode->streams, node); + } else if (g_strcmp0 (element_name, "segment") == 0) { + GstValidateMediaStreamNode *streamnode = filenode->streams->data; + GstValidateSegmentNode *node = + deserialize_segmentnode (attribute_names, attribute_values); + + streamnode->segments = g_list_append (streamnode->segments, node); + + } else if (g_strcmp0 (element_name, "frame") == 0) { + GstValidateMediaStreamNode *streamnode = filenode->streams->data; + + streamnode->cframe = streamnode->frames = + g_list_insert_sorted (streamnode->frames, + deserialize_framenode (attribute_names, attribute_values), + (GCompareFunc) compare_frames); + } else if (g_strcmp0 (element_name, "tags") == 0) { + if (priv->in_stream) { + GstValidateMediaStreamNode *snode = (GstValidateMediaStreamNode *) + filenode->streams->data; + + snode->tags = deserialize_tagsnode (attribute_names, attribute_values); + } else { + filenode->tags = deserialize_tagsnode (attribute_names, attribute_values); + } + } else if (g_strcmp0 (element_name, "tag") == 0) { + GstValidateMediaTagsNode *tagsnode; + + if (priv->in_stream) { + GstValidateMediaStreamNode *snode = (GstValidateMediaStreamNode *) + filenode->streams->data; + tagsnode = snode->tags; + } else { + tagsnode = filenode->tags; + } + + tagsnode->tags = g_list_prepend (tagsnode->tags, + deserialize_tagnode (attribute_names, attribute_values)); + } +} + +static void +on_error_cb (GMarkupParseContext * context, GError * error, gpointer user_data) +{ + GST_ERROR ("Error parsing file: %s", error->message); +} + +static const GMarkupParser content_parser = { + on_start_element_cb, + on_end_element_cb, + NULL, + NULL, + &on_error_cb +}; + +static gboolean +_set_content (GstValidateMediaDescriptorParser * parser, + const gchar * content, gsize size, GError ** error) +{ + GError *err = NULL; + GstValidateMediaDescriptorParserPrivate *priv = parser->priv; + + priv->parsecontext = g_markup_parse_context_new (&content_parser, + G_MARKUP_TREAT_CDATA_AS_TEXT, parser, NULL); + + if (g_markup_parse_context_parse (priv->parsecontext, content, + size, &err) == FALSE) + goto failed; + + return TRUE; + +failed: + g_propagate_error (error, err); + return FALSE; +} + +static gboolean +set_xml_path (GstValidateMediaDescriptorParser * parser, const gchar * path, + GError ** error) +{ + gsize xmlsize; + gchar *content; + GError *err = NULL; + GstValidateMediaDescriptorParserPrivate *priv = parser->priv; + gboolean result; + + if (!g_file_get_contents (path, &content, &xmlsize, &err)) + goto failed; + + priv->xmlpath = g_strdup (path); + + result = _set_content (parser, content, xmlsize, error); + g_free (content); + return result; + +failed: + g_propagate_error (error, err); + return FALSE; +} + +/* GObject standard vmethods */ +static void +dispose (GstValidateMediaDescriptorParser * parser) +{ + G_OBJECT_CLASS (gst_validate_media_descriptor_parser_parent_class)->dispose + (G_OBJECT (parser)); +} + +static void +finalize (GstValidateMediaDescriptorParser * parser) +{ + GstValidateMediaDescriptorParserPrivate *priv; + + priv = parser->priv; + + g_free (priv->xmlpath); + g_free (priv->xmlcontent); + + if (priv->parsecontext != NULL) + g_markup_parse_context_free (priv->parsecontext); + + G_OBJECT_CLASS (gst_validate_media_descriptor_parser_parent_class)->finalize + (G_OBJECT (parser)); +} + + +static void +get_property (GObject * gobject, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + switch (prop_id) { + default: + g_assert_not_reached (); + } + +} + +static void +set_property (GObject * gobject, guint prop_id, const GValue * value, + GParamSpec * pspec) +{ + switch (prop_id) { + default: + g_assert_not_reached (); + } +} + +static void +gst_validate_media_descriptor_parser_init (GstValidateMediaDescriptorParser * + parser) +{ + GstValidateMediaDescriptorParserPrivate *priv; + + parser->priv = priv = + gst_validate_media_descriptor_parser_get_instance_private (parser); + + priv->xmlpath = NULL; +} + +static void + gst_validate_media_descriptor_parser_class_init + (GstValidateMediaDescriptorParserClass * self_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (self_class); + + object_class->dispose = (void (*)(GObject * object)) dispose; + object_class->finalize = (void (*)(GObject * object)) finalize; + object_class->get_property = get_property; + object_class->set_property = set_property; +} + +/* Public methods */ +GstValidateMediaDescriptorParser * +gst_validate_media_descriptor_parser_new (GstValidateRunner * runner, + const gchar * xmlpath, GError ** error) +{ + GstValidateMediaDescriptorParser *parser; + + parser = + g_object_new (GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR_PARSER, + "validate-runner", runner, NULL); + + if (set_xml_path (parser, xmlpath, error) == FALSE) { + g_object_unref (parser); + + return NULL; + } + + + return parser; +} + +GstValidateMediaDescriptorParser * +gst_validate_media_descriptor_parser_new_from_xml (GstValidateRunner * runner, + const gchar * xml, GError ** error) +{ + GstValidateMediaDescriptorParser *parser; + + parser = + g_object_new (GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR_PARSER, + "validate-runner", runner, NULL); + if (_set_content (parser, xml, strlen (xml) * sizeof (gchar), error) == FALSE) { + g_object_unref (parser); + + return NULL; + } + + + return parser; +} + +gchar *gst_validate_media_descriptor_parser_get_xml_path + (GstValidateMediaDescriptorParser * parser) +{ + g_return_val_if_fail (GST_IS_VALIDATE_MEDIA_DESCRIPTOR_PARSER (parser), NULL); + + return g_strdup (parser->priv->xmlpath); +} + +gboolean + gst_validate_media_descriptor_parser_add_stream + (GstValidateMediaDescriptorParser * parser, GstPad * pad) { + GList *tmp; + gboolean ret = FALSE; + GstCaps *caps; + + g_return_val_if_fail (GST_IS_VALIDATE_MEDIA_DESCRIPTOR_PARSER (parser), + FALSE); + g_return_val_if_fail (((GstValidateMediaDescriptor *) parser)->filenode, + FALSE); + + caps = gst_pad_query_caps (pad, NULL); + for (tmp = ((GstValidateMediaDescriptor *) parser)->filenode->streams; tmp; + tmp = tmp->next) { + GstValidateMediaStreamNode *streamnode = (GstValidateMediaStreamNode *) + tmp->data; + + if (streamnode->pad == NULL && gst_caps_is_equal (streamnode->caps, caps)) { + ret = TRUE; + streamnode->pad = gst_object_ref (pad); + + goto done; + } + } + +done: + if (caps != NULL) + gst_caps_unref (caps); + + return ret; +} + +gboolean + gst_validate_media_descriptor_parser_all_stream_found + (GstValidateMediaDescriptorParser * parser) { + GList *tmp; + + g_return_val_if_fail (GST_IS_VALIDATE_MEDIA_DESCRIPTOR_PARSER (parser), + FALSE); + g_return_val_if_fail (((GstValidateMediaDescriptor *) parser)->filenode, + FALSE); + + for (tmp = ((GstValidateMediaDescriptor *) parser)->filenode->streams; tmp; + tmp = tmp->next) { + GstValidateMediaStreamNode *streamnode = (GstValidateMediaStreamNode *) + tmp->data; + + if (streamnode->pad == NULL) + return FALSE; + + } + + return TRUE; +} + +gboolean + gst_validate_media_descriptor_parser_add_taglist + (GstValidateMediaDescriptorParser * parser, GstTagList * taglist) { + GList *tmptag; + GstValidateMediaTagsNode *tagsnode; + + g_return_val_if_fail (GST_IS_VALIDATE_MEDIA_DESCRIPTOR_PARSER (parser), + FALSE); + g_return_val_if_fail (((GstValidateMediaDescriptor *) parser)->filenode, + FALSE); + g_return_val_if_fail (GST_IS_STRUCTURE (taglist), FALSE); + + tagsnode = ((GstValidateMediaDescriptor *) parser)->filenode->tags; + + for (tmptag = tagsnode->tags; tmptag; tmptag = tmptag->next) { + if (gst_validate_tag_node_compare ((GstValidateMediaTagNode *) + tmptag->data, taglist)) { + GST_DEBUG ("Adding tag %" GST_PTR_FORMAT, taglist); + return TRUE; + } + } + + return FALSE; +} + +gboolean + gst_validate_media_descriptor_parser_all_tags_found + (GstValidateMediaDescriptorParser * parser) { + GList *tmptag; + GstValidateMediaTagsNode *tagsnode; + gboolean ret = TRUE; + + g_return_val_if_fail (GST_IS_VALIDATE_MEDIA_DESCRIPTOR_PARSER (parser), + FALSE); + g_return_val_if_fail (((GstValidateMediaDescriptor *) parser)->filenode, + FALSE); + + tagsnode = ((GstValidateMediaDescriptor *) parser)->filenode->tags; + for (tmptag = tagsnode->tags; tmptag; tmptag = tmptag->next) { + gchar *tag = NULL; + + tag = gst_tag_list_to_string (((GstValidateMediaTagNode *) + tmptag->data)->taglist); + if (((GstValidateMediaTagNode *) + tmptag->data)->found == FALSE) { + + if (((GstValidateMediaTagNode *) + tmptag->data)->taglist != NULL) { + GST_DEBUG ("Tag not found %s", tag); + } else { + GST_DEBUG ("Tag not properly deserialized"); + } + + ret = FALSE; + } + + GST_DEBUG ("Tag properly found %s", tag); + g_free (tag); + } + + return ret; +} diff --git a/validate/gst/validate/media-descriptor-parser.h b/validate/gst/validate/media-descriptor-parser.h new file mode 100644 index 0000000000..b10dc05fbd --- /dev/null +++ b/validate/gst/validate/media-descriptor-parser.h @@ -0,0 +1,83 @@ +/* GstValidate + * + * Copyright (c) 2012, Collabora Ltd + * Author: Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef GST_VALIDATE_MEDIA_DESCRIPTOR_PARSER_h +#define GST_VALIDATE_MEDIA_DESCRIPTOR_PARSER_h + +#include +#include +#include +#include "media-descriptor.h" + +G_BEGIN_DECLS + +GST_VALIDATE_API +GType gst_validate_media_descriptor_parser_get_type (void); + +#ifndef __GI_SCANNER__ +#define GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR_PARSER (gst_validate_media_descriptor_parser_get_type ()) +#define GST_VALIDATE_MEDIA_DESCRIPTOR_PARSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR_PARSER, GstValidateMediaDescriptorParser)) +#define GST_VALIDATE_MEDIA_DESCRIPTOR_PARSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR_PARSER, GstValidateMediaDescriptorParserClass)) +#define GST_IS_VALIDATE_MEDIA_DESCRIPTOR_PARSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR_PARSER)) +#define GST_IS_VALIDATE_MEDIA_DESCRIPTOR_PARSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR_PARSER)) +#define GST_VALIDATE_MEDIA_DESCRIPTOR_PARSER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR_PARSER, GstValidateMediaDescriptorParserClass)) +#endif + +typedef struct _GstValidateMediaDescriptorParserPrivate GstValidateMediaDescriptorParserPrivate; + + +typedef struct { + GstValidateMediaDescriptor parent; + + GstValidateMediaDescriptorParserPrivate *priv; + +} GstValidateMediaDescriptorParser; + +typedef struct { + + GstValidateMediaDescriptorClass parent; + +} GstValidateMediaDescriptorParserClass; + +GST_VALIDATE_API +GstValidateMediaDescriptorParser * gst_validate_media_descriptor_parser_new (GstValidateRunner *runner, + const gchar * xmlpath, + GError **error); +GST_VALIDATE_API GstValidateMediaDescriptorParser * +gst_validate_media_descriptor_parser_new_from_xml (GstValidateRunner * runner, + const gchar * xml, + GError ** error); +GST_VALIDATE_API +gchar * gst_validate_media_descriptor_parser_get_xml_path (GstValidateMediaDescriptorParser *parser); +GST_VALIDATE_API +gboolean gst_validate_media_descriptor_parser_add_stream (GstValidateMediaDescriptorParser *parser, + GstPad *pad); +GST_VALIDATE_API +gboolean gst_validate_media_descriptor_parser_add_taglist (GstValidateMediaDescriptorParser *parser, + GstTagList *taglist); +GST_VALIDATE_API +gboolean gst_validate_media_descriptor_parser_all_stream_found (GstValidateMediaDescriptorParser *parser); +GST_VALIDATE_API +gboolean gst_validate_media_descriptor_parser_all_tags_found (GstValidateMediaDescriptorParser *parser); + +G_END_DECLS + +#endif /* GST_VALIDATE_MEDIA_DESCRIPTOR_PARSER_h */ diff --git a/validate/gst/validate/media-descriptor-writer.c b/validate/gst/validate/media-descriptor-writer.c new file mode 100644 index 0000000000..1bd9d44979 --- /dev/null +++ b/validate/gst/validate/media-descriptor-writer.c @@ -0,0 +1,1030 @@ +/* GstValidate + * + * Copyright (c) 2012, Collabora Ltd. + * Author: Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include "media-descriptor-writer.h" +#include + +struct _GstValidateMediaDescriptorWriterPrivate +{ + GstElement *pipeline; + GstCaps *raw_caps; + GMainLoop *loop; + + GList *parsers; + GstValidateMediaDescriptorWriterFlags flags; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GstValidateMediaDescriptorWriter, + gst_validate_media_descriptor_writer, GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR); + +#define STR_APPEND(arg, nb_white) \ + g_string_append_printf (res, "%*s%s%s", (nb_white), " ", (arg), "\n"); \ + +#define STR_APPEND0(arg) STR_APPEND((arg), 0) +#define STR_APPEND1(arg) STR_APPEND((arg), 2) +#define STR_APPEND2(arg) STR_APPEND((arg), 4) +#define STR_APPEND3(arg) STR_APPEND((arg), 6) +#define STR_APPEND4(arg) STR_APPEND((arg), 8) + +#define FLAG_IS_SET(writer,flag) ((writer->priv->flags & (flag)) == (flag)) + +enum +{ + PROP_0, + PROP_PATH, + N_PROPERTIES +}; + +static void +finalize (GstValidateMediaDescriptorWriter * writer) +{ + if (writer->priv->raw_caps) + gst_caps_unref (writer->priv->raw_caps); + + if (writer->priv->parsers) + gst_plugin_feature_list_free (writer->priv->parsers); + + G_OBJECT_CLASS (gst_validate_media_descriptor_writer_parent_class)->finalize + (G_OBJECT (writer)); +} + +static void +get_property (GObject * gobject, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + switch (prop_id) { + default: + g_assert_not_reached (); + } + +} + +static void +set_property (GObject * gobject, guint prop_id, const GValue * value, + GParamSpec * pspec) +{ + switch (prop_id) { + default: + g_assert_not_reached (); + } +} + +static void +gst_validate_media_descriptor_writer_init (GstValidateMediaDescriptorWriter * + writer) +{ + GstValidateMediaDescriptorWriterPrivate *priv; + + + writer->priv = priv = + gst_validate_media_descriptor_writer_get_instance_private (writer); + + writer->priv->parsers = + gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_PARSER, + GST_RANK_MARGINAL); +} + +static void + gst_validate_media_descriptor_writer_class_init + (GstValidateMediaDescriptorWriterClass * self_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (self_class); + + object_class->finalize = (void (*)(GObject * object)) finalize; + object_class->get_property = get_property; + object_class->set_property = set_property; +} + +/* Private methods */ +static gchar * +serialize_filenode (GstValidateMediaDescriptorWriter * writer) +{ + GString *res; + gchar *tmpstr, *caps_str; + GList *tmp, *tmp2; + GstValidateMediaTagsNode *tagsnode; + GstValidateMediaFileNode + * filenode = ((GstValidateMediaDescriptor *) writer)->filenode; + + tmpstr = g_markup_printf_escaped ("\n", + filenode->duration, filenode->frame_detection, filenode->skip_parsers, + filenode->uri, filenode->seekable ? "true" : "false"); + + if (filenode->caps) + caps_str = gst_caps_to_string (filenode->caps); + else + caps_str = g_strdup (""); + + res = g_string_new (tmpstr); + g_free (tmpstr); + tmpstr = g_markup_printf_escaped (" \n", caps_str); + g_string_append (res, tmpstr); + g_free (tmpstr); + g_free (caps_str); + for (tmp = filenode->streams; tmp; tmp = tmp->next) { + GList *tmp3; + GstValidateMediaStreamNode + * snode = ((GstValidateMediaStreamNode *) tmp->data); + + + STR_APPEND2 (snode->str_open); + + /* Segment are always prepended, let's bring them back to the right order */ + STR_APPEND3 (""); + for (tmp2 = snode->segments; tmp2; tmp2 = tmp2->next) + STR_APPEND4 (((GstValidateSegmentNode *) tmp2->data)->str_open); + STR_APPEND3 (""); + + for (tmp2 = snode->frames; tmp2; tmp2 = tmp2->next) { + STR_APPEND3 (((GstValidateMediaFrameNode *) tmp2->data)->str_open); + } + + tagsnode = snode->tags; + if (tagsnode) { + STR_APPEND3 (tagsnode->str_open); + for (tmp3 = tagsnode->tags; tmp3; tmp3 = tmp3->next) { + STR_APPEND4 (((GstValidateMediaTagNode *) tmp3->data)->str_open); + } + STR_APPEND3 (tagsnode->str_close); + } + + STR_APPEND2 (snode->str_close); + } + STR_APPEND1 (""); + + tagsnode = filenode->tags; + if (tagsnode) { + STR_APPEND1 (tagsnode->str_open); + for (tmp2 = tagsnode->tags; tmp2; tmp2 = tmp2->next) { + STR_APPEND2 (((GstValidateMediaTagNode *) + tmp2->data)->str_open); + } + STR_APPEND1 (tagsnode->str_close); + } + + g_string_append (res, filenode->str_close); + + return g_string_free (res, FALSE); +} + +/* Should be called with GST_VALIDATE_MEDIA_DESCRIPTOR_LOCK */ +static + GstValidateMediaStreamNode + * gst_validate_media_descriptor_find_stream_node_by_pad + (GstValidateMediaDescriptor * md, GstPad * pad) +{ + GList *tmp; + + for (tmp = md->filenode->streams; tmp; tmp = tmp->next) { + GstValidateMediaStreamNode + * streamnode = (GstValidateMediaStreamNode *) tmp->data; + + if (streamnode->pad == pad) { + return streamnode; + } + } + + return NULL; +} + +/* Public methods */ +GstValidateMediaDescriptorWriter * +gst_validate_media_descriptor_writer_new (GstValidateRunner * runner, + const gchar * uri, GstClockTime duration, gboolean seekable) +{ + GstValidateMediaDescriptorWriter *writer; + GstValidateMediaFileNode *fnode; + + writer = + g_object_new (GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR_WRITER, + "validate-runner", runner, NULL); + + fnode = ((GstValidateMediaDescriptor *) writer)->filenode; + fnode->uri = g_strdup (uri); + fnode->duration = duration; + fnode->seekable = seekable; + fnode->str_open = NULL; + + fnode->str_close = g_markup_printf_escaped (""); + + return writer; +} + +static GstCaps * +strip_caps_to_avoid_parsers (GstValidateMediaDescriptorWriter * writer, + GstCaps * caps) +{ + gint i; + GstStructure *structure, *new_struct; + GstCaps *stripped; + + /* If parsers are wanted, use exactly the caps reported by the discoverer (which also + * plugs parsers). */ + if (!FLAG_IS_SET (writer, + GST_VALIDATE_MEDIA_DESCRIPTOR_WRITER_FLAGS_NO_PARSER)) + return gst_caps_copy (caps); + + /* Otherwise use the simplest version of those caps (with the names only), + * meaning that decodebin will never plug any parser */ + stripped = gst_caps_new_empty (); + for (i = 0; i < gst_caps_get_size (caps); i++) { + structure = gst_caps_get_structure (caps, i); + new_struct = gst_structure_new_empty (gst_structure_get_name (structure)); + + gst_caps_append_structure (stripped, new_struct); + } + + return stripped; +} + +static gboolean + gst_validate_media_descriptor_writer_add_stream + (GstValidateMediaDescriptorWriter * writer, GstDiscovererStreamInfo * info) +{ + const gchar *stype; + gboolean ret = FALSE; + GstCaps *caps; + gchar *capsstr = NULL; + GstValidateMediaStreamNode *snode = NULL; + + g_return_val_if_fail (GST_IS_VALIDATE_MEDIA_DESCRIPTOR_WRITER (writer), + FALSE); + g_return_val_if_fail (((GstValidateMediaDescriptor *) writer)->filenode, + FALSE); + + snode = g_slice_new0 (GstValidateMediaStreamNode); + snode->frames = NULL; + snode->cframe = NULL; + + snode->id = g_strdup (gst_discoverer_stream_info_get_stream_id (info)); + if (snode->id == NULL) { + caps = gst_discoverer_stream_info_get_caps (info); + capsstr = gst_caps_to_string (caps); + + g_slice_free (GstValidateMediaStreamNode, snode); + GST_VALIDATE_REPORT (writer, FILE_NO_STREAM_ID, + "Stream with caps: %s has no stream ID", capsstr); + gst_caps_unref (caps); + g_free (capsstr); + + return FALSE; + } + + caps = gst_discoverer_stream_info_get_caps (info); + snode->caps = caps; /* Pass ownership */ + capsstr = gst_caps_to_string (caps); + if (GST_IS_DISCOVERER_AUDIO_INFO (info)) { + stype = "audio"; + } else if (GST_IS_DISCOVERER_VIDEO_INFO (info)) { + if (gst_discoverer_video_info_is_image (GST_DISCOVERER_VIDEO_INFO (info))) + stype = "image"; + else + stype = "video"; + } else if (GST_IS_DISCOVERER_SUBTITLE_INFO (info)) { + stype = "subtitle"; + } else { + stype = "Unknown"; + } + + snode->str_open = + g_markup_printf_escaped + ("", stype, capsstr, snode->id); + + snode->str_close = g_markup_printf_escaped (""); + + ((GstValidateMediaDescriptor *) writer)->filenode->streams = + g_list_prepend (((GstValidateMediaDescriptor *) writer)-> + filenode->streams, snode); + + if (gst_discoverer_stream_info_get_tags (info)) { + gst_validate_media_descriptor_writer_add_tags (writer, snode->id, + gst_discoverer_stream_info_get_tags (info)); + } + + if (writer->priv->raw_caps == NULL) + writer->priv->raw_caps = strip_caps_to_avoid_parsers (writer, caps); + else { + writer->priv->raw_caps = gst_caps_merge (writer->priv->raw_caps, + strip_caps_to_avoid_parsers (writer, caps)); + } + g_free (capsstr); + + return ret; +} + +static GstPadProbeReturn +_uridecodebin_probe (GstPad * pad, GstPadProbeInfo * info, + GstValidateMediaDescriptorWriter * writer) +{ + if (GST_PAD_PROBE_INFO_TYPE (info) & GST_PAD_PROBE_TYPE_BUFFER) { + gst_validate_media_descriptor_writer_add_frame (writer, pad, info->data); + } else if (GST_PAD_PROBE_INFO_TYPE (info) & + GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM) { + GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info); + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEGMENT:{ + const GstSegment *segment; + GstValidateMediaStreamNode *streamnode; + + streamnode = + gst_validate_media_descriptor_find_stream_node_by_pad ( + (GstValidateMediaDescriptor *) + writer, pad); + if (streamnode) { + GstValidateSegmentNode *segment_node = + g_slice_new0 (GstValidateSegmentNode); + + gst_event_parse_segment (event, &segment); + gst_segment_copy_into (segment, &segment_node->segment); + segment_node->next_frame_id = g_list_length (streamnode->frames); + + segment_node->str_open = + g_markup_printf_escaped ("", segment_node->next_frame_id, + segment->flags, segment->rate, segment->applied_rate, + segment->format, segment->base, segment->offset, segment->start, + segment->stop, segment->time, segment->position, + segment->duration); + + streamnode->segments = + g_list_prepend (streamnode->segments, segment_node); + } + break; + } + default: + break; + } + } else { + g_assert_not_reached (); + } + + return GST_PAD_PROBE_OK; +} + +static gboolean +_find_stream_id (GstPad * pad, GstEvent ** event, + GstValidateMediaDescriptorWriter * writer) +{ + if (GST_EVENT_TYPE (*event) == GST_EVENT_STREAM_START) { + GList *tmp; + GstValidateMediaStreamNode *snode = NULL; + const gchar *stream_id; + + gst_event_parse_stream_start (*event, &stream_id); + for (tmp = ((GstValidateMediaDescriptor *) writer)->filenode->streams; tmp; + tmp = tmp->next) { + GstValidateMediaStreamNode *subnode = + (GstValidateMediaStreamNode *) tmp->data; + if (g_strcmp0 (subnode->id, stream_id) == 0) { + snode = subnode; + + break; + } + } + + if (!snode || snode->pad) { + GST_VALIDATE_REPORT (writer, FILE_NO_STREAM_ID, + "Got pad %s:%s where Discoverer found no stream ID", + GST_DEBUG_PAD_NAME (pad)); + + return TRUE; + } + + snode->pad = gst_object_ref (pad); + + return FALSE; + } + + return TRUE; +} + +static inline GstElement * +_get_parser (GstValidateMediaDescriptorWriter * writer, GstPad * pad) +{ + GList *parsers1, *parsers; + GstElement *parser = NULL; + GstElementFactory *parserfact = NULL; + GstCaps *format; + + if (FLAG_IS_SET (writer, + GST_VALIDATE_MEDIA_DESCRIPTOR_WRITER_FLAGS_NO_PARSER)) + return NULL; + + format = gst_pad_get_current_caps (pad); + + GST_DEBUG ("Getting list of parsers for format %" GST_PTR_FORMAT, format); + parsers1 = + gst_element_factory_list_filter (writer->priv->parsers, format, + GST_PAD_SRC, FALSE); + parsers = + gst_element_factory_list_filter (parsers1, format, GST_PAD_SINK, FALSE); + gst_plugin_feature_list_free (parsers1); + + if (G_UNLIKELY (parsers == NULL)) { + GST_DEBUG ("Couldn't find any compatible parsers"); + goto beach; + } + + /* Just pick the first one */ + parserfact = parsers->data; + if (parserfact) + parser = gst_element_factory_create (parserfact, NULL); + + gst_plugin_feature_list_free (parsers); + +beach: + if (format) + gst_caps_unref (format); + + return parser; +} + +static void +pad_added_cb (GstElement * decodebin, GstPad * pad, + GstValidateMediaDescriptorWriter * writer) +{ + GstValidateMediaStreamNode *snode = NULL; + GstPad *sinkpad, *srcpad; + + /* Try to plug a parser so we have as much info as possible + * about the encoded stream. */ + GstElement *parser = _get_parser (writer, pad); + GstElement *fakesink = gst_element_factory_make ("fakesink", NULL); + + if (parser) { + sinkpad = gst_element_get_static_pad (parser, "sink"); + gst_bin_add (GST_BIN (writer->priv->pipeline), parser); + gst_element_sync_state_with_parent (parser); + gst_pad_link (pad, sinkpad); + gst_object_unref (sinkpad); + + srcpad = gst_element_get_static_pad (parser, "src"); + } else { + srcpad = gst_object_ref (pad); + } + + sinkpad = gst_element_get_static_pad (fakesink, "sink"); + gst_bin_add (GST_BIN (writer->priv->pipeline), fakesink); + gst_element_sync_state_with_parent (fakesink); + gst_pad_link (srcpad, sinkpad); + gst_object_unref (sinkpad); + gst_pad_sticky_events_foreach (pad, + (GstPadStickyEventsForeachFunction) _find_stream_id, writer); + + if (srcpad != pad) { + snode = + gst_validate_media_descriptor_find_stream_node_by_pad ( + (GstValidateMediaDescriptor *) + writer, pad); + if (snode) { + gst_object_unref (snode->pad); + snode->pad = gst_object_ref (srcpad); + } + } + + gst_pad_add_probe (srcpad, + GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, + (GstPadProbeCallback) _uridecodebin_probe, writer, NULL); + + gst_object_unref (srcpad); +} + +static gboolean +bus_callback (GstBus * bus, GstMessage * message, + GstValidateMediaDescriptorWriter * writer) +{ + GMainLoop *loop = writer->priv->loop; + + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ERROR: + { + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (writer->priv->pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, "gst-validate-media-check.error"); + g_main_loop_quit (loop); + break; + } + case GST_MESSAGE_EOS: + GST_INFO ("Got EOS!"); + g_main_loop_quit (loop); + break; + case GST_MESSAGE_STATE_CHANGED: + if (GST_MESSAGE_SRC (message) == GST_OBJECT (writer->priv->pipeline)) { + GstState oldstate, newstate, pending; + + gst_message_parse_state_changed (message, &oldstate, &newstate, + &pending); + + GST_DEBUG ("State changed (old: %s, new: %s, pending: %s)", + gst_element_state_get_name (oldstate), + gst_element_state_get_name (newstate), + gst_element_state_get_name (pending)); + + if (newstate == GST_STATE_PLAYING) { + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (writer->priv->pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, + "gst-validate-media-descriptor-writer.playing"); + } + } + + break; + case GST_MESSAGE_BUFFERING:{ + gint percent; + + gst_message_parse_buffering (message, &percent); + + /* no state management needed for live pipelines */ + if (percent == 100) { + gst_element_set_state (writer->priv->pipeline, GST_STATE_PLAYING); + } else { + gst_element_set_state (writer->priv->pipeline, GST_STATE_PAUSED); + } + break; + } + default: + break; + } + + return TRUE; +} + +static gboolean +_run_frame_analysis (GstValidateMediaDescriptorWriter * writer, + GstValidateRunner * runner, const gchar * uri) +{ + GstBus *bus; + GList *tmp; + GstStateChangeReturn sret; + GstValidateMonitor *monitor; + GstValidateMediaFileNode *filenode; + + GstElement *uridecodebin = gst_element_factory_make ("uridecodebin", NULL); + + writer->priv->pipeline = gst_pipeline_new ("frame-analysis"); + + monitor = + gst_validate_monitor_factory_create (GST_OBJECT_CAST (writer-> + priv->pipeline), runner, NULL); + gst_validate_reporter_set_handle_g_logs (GST_VALIDATE_REPORTER (monitor)); + + g_object_set (uridecodebin, "uri", uri, "caps", writer->priv->raw_caps, NULL); + g_signal_connect (uridecodebin, "pad-added", G_CALLBACK (pad_added_cb), + writer); + gst_bin_add (GST_BIN (writer->priv->pipeline), uridecodebin); + + writer->priv->loop = g_main_loop_new (NULL, FALSE); + bus = gst_element_get_bus (writer->priv->pipeline); + gst_bus_add_signal_watch (bus); + g_signal_connect (bus, "message", (GCallback) bus_callback, writer); + sret = gst_element_set_state (writer->priv->pipeline, GST_STATE_PLAYING); + switch (sret) { + case GST_STATE_CHANGE_FAILURE: + /* ignore, we should get an error message posted on the bus */ + gst_validate_printf (NULL, "Pipeline failed to go to PLAYING state\n"); + return FALSE; + default: + break; + } + + g_main_loop_run (writer->priv->loop); + + filenode = ((GstValidateMediaDescriptor *) writer)->filenode; + /* Segment are always prepended, let's reorder them. */ + for (tmp = filenode->streams; tmp; tmp = tmp->next) { + GstValidateMediaStreamNode + * snode = ((GstValidateMediaStreamNode *) tmp->data); + snode->segments = g_list_reverse (snode->segments); + } + + gst_element_set_state (writer->priv->pipeline, GST_STATE_NULL); + gst_object_unref (writer->priv->pipeline); + writer->priv->pipeline = NULL; + g_main_loop_unref (writer->priv->loop); + writer->priv->loop = NULL; + gst_bus_remove_signal_watch (bus); + gst_object_unref (bus); + gst_validate_reporter_purge_reports (GST_VALIDATE_REPORTER (monitor)); + g_object_unref (monitor); + + return TRUE; +} + +GstValidateMediaDescriptorWriter * +gst_validate_media_descriptor_writer_new_discover (GstValidateRunner * runner, + const gchar * uri, GstValidateMediaDescriptorWriterFlags flags, + GError ** err) +{ + GList *tmp, *streams = NULL; + GstDiscovererInfo *info = NULL; + GstDiscoverer *discoverer; + GstDiscovererStreamInfo *streaminfo = NULL; + GstValidateMediaDescriptorWriter *writer = NULL; + GstValidateMediaDescriptor *media_descriptor; + const GstTagList *tags; + GError *error = NULL; + + discoverer = gst_discoverer_new (GST_SECOND * 60, &error); + + if (discoverer == NULL) { + GST_ERROR ("Could not create discoverer"); + g_propagate_error (err, error); + return NULL; + } + + info = gst_discoverer_discover_uri (discoverer, uri, &error); + + if (error) { + GST_ERROR ("Could not discover URI: %s (error: %s)", uri, error->message); + g_propagate_error (err, error); + goto out; + } else { + GstDiscovererResult result = gst_discoverer_info_get_result (info); + switch (result) { + case GST_DISCOVERER_OK: + break; + case GST_DISCOVERER_URI_INVALID: + GST_ERROR ("URI is not valid"); + goto out; + case GST_DISCOVERER_TIMEOUT: + GST_ERROR ("Analyzing URI timed out\n"); + goto out; + case GST_DISCOVERER_BUSY: + GST_ERROR ("Discoverer was busy\n"); + goto out; + case GST_DISCOVERER_MISSING_PLUGINS: + { + gint i = 0; + const gchar **installer_details = + gst_discoverer_info_get_missing_elements_installer_details (info); + GST_ERROR ("Missing plugins"); + while (installer_details[i]) { + GST_ERROR ("(%s)", installer_details[i]); + i++; + } + + goto out; + } + default: + break; + } + } + + streaminfo = gst_discoverer_info_get_stream_info (info); + + if (streaminfo) { + writer = + gst_validate_media_descriptor_writer_new (runner, + gst_discoverer_info_get_uri (info), + gst_discoverer_info_get_duration (info), + gst_discoverer_info_get_seekable (info)); + + writer->priv->flags = flags; + if (FLAG_IS_SET (writer, + GST_VALIDATE_MEDIA_DESCRIPTOR_WRITER_FLAGS_HANDLE_GLOGS)) + gst_validate_reporter_set_handle_g_logs (GST_VALIDATE_REPORTER (writer)); + + tags = gst_discoverer_info_get_tags (info); + if (tags) + gst_validate_media_descriptor_writer_add_taglist (writer, tags); + + if (GST_IS_DISCOVERER_CONTAINER_INFO (streaminfo)) { + ((GstValidateMediaDescriptor *) writer)->filenode->caps = + gst_discoverer_stream_info_get_caps (GST_DISCOVERER_STREAM_INFO + (streaminfo)); + + streams = gst_discoverer_info_get_stream_list (info); + for (tmp = streams; tmp; tmp = tmp->next) { + GstDiscovererStreamInfo *streaminfo = + (GstDiscovererStreamInfo *) tmp->data; + gst_validate_media_descriptor_writer_add_stream (writer, streaminfo); + } + } else { + if (!GST_IS_DISCOVERER_AUDIO_INFO (info) + && !GST_IS_DISCOVERER_AUDIO_INFO (info) + && gst_discoverer_stream_info_get_next (streaminfo)) { + ((GstValidateMediaDescriptor *) writer)->filenode->caps = + gst_discoverer_stream_info_get_caps (streaminfo); + streaminfo = gst_discoverer_stream_info_get_next (streaminfo); + } + do { + gst_validate_media_descriptor_writer_add_stream (writer, streaminfo); + } while ((streaminfo = gst_discoverer_stream_info_get_next (streaminfo))); + } + } else { + GST_VALIDATE_REPORT (writer, FILE_NO_STREAM_INFO, + "Discoverer info, does not contain the stream info"); + goto out; + } + + media_descriptor = (GstValidateMediaDescriptor *) writer; + if (streams == NULL && media_descriptor->filenode->caps) + writer->priv->raw_caps = gst_caps_copy (media_descriptor->filenode->caps); + + gst_discoverer_stream_info_list_free (streams); + + + if (FLAG_IS_SET (writer, GST_VALIDATE_MEDIA_DESCRIPTOR_WRITER_FLAGS_FULL)) + _run_frame_analysis (writer, runner, uri); + +out: + if (info) + gst_discoverer_info_unref (info); + if (streaminfo) + gst_discoverer_stream_info_unref (streaminfo); + g_object_unref (discoverer); + return writer; +} + +gboolean +gst_validate_media_descriptor_writer_add_tags (GstValidateMediaDescriptorWriter + * writer, const gchar * stream_id, const GstTagList * taglist) +{ + GstValidateMediaTagsNode *tagsnode; + GstValidateMediaTagNode *tagnode; + GList *tmp, *tmptag; + + gchar *str_str = NULL; + GstValidateMediaStreamNode *snode = NULL; + + g_return_val_if_fail (GST_IS_VALIDATE_MEDIA_DESCRIPTOR_WRITER (writer), + FALSE); + g_return_val_if_fail (((GstValidateMediaDescriptor *) writer)->filenode, + FALSE); + + for (tmp = ((GstValidateMediaDescriptor *) writer)->filenode->streams; tmp; + tmp = tmp->next) { + GstValidateMediaStreamNode *subnode = + (GstValidateMediaStreamNode *) tmp->data; + if (g_strcmp0 (subnode->id, stream_id) == 0) { + snode = subnode; + + break; + } + } + + if (snode == NULL) { + GST_WARNING ("Could not find stream with id: %s", stream_id); + + return FALSE; + } + + if (snode->tags == NULL) { + tagsnode = g_slice_new0 (GstValidateMediaTagsNode); + tagsnode->str_open = g_markup_printf_escaped (""); + tagsnode->str_close = g_markup_printf_escaped (""); + snode->tags = tagsnode; + } else { + tagsnode = snode->tags; + + for (tmptag = tagsnode->tags; tmptag; tmptag = tmptag->next) { + if (gst_validate_tag_node_compare ((GstValidateMediaTagNode *) + tmptag->data, taglist)) { + GST_DEBUG ("Tag already in... not adding again %" GST_PTR_FORMAT, + taglist); + return TRUE; + } + } + } + + tagnode = g_slice_new0 (GstValidateMediaTagNode); + tagnode->taglist = gst_tag_list_copy (taglist); + str_str = gst_tag_list_to_string (tagnode->taglist); + tagnode->str_open = + g_markup_printf_escaped ("", str_str); + tagsnode->tags = g_list_prepend (tagsnode->tags, tagnode); + + g_free (str_str); + + return FALSE; +} + +gboolean +gst_validate_media_descriptor_writer_add_pad (GstValidateMediaDescriptorWriter * + writer, GstPad * pad) +{ + GList *tmp; + gboolean ret = FALSE; + GstCaps *caps; + gchar *capsstr = NULL, *padname = NULL; + GstValidateMediaStreamNode *snode = NULL; + + g_return_val_if_fail (GST_IS_VALIDATE_MEDIA_DESCRIPTOR_WRITER (writer), + FALSE); + g_return_val_if_fail (((GstValidateMediaDescriptor *) writer)->filenode, + FALSE); + + caps = gst_pad_get_current_caps (pad); + for (tmp = ((GstValidateMediaDescriptor *) writer)->filenode->streams; tmp; + tmp = tmp->next) { + GstValidateMediaStreamNode + * streamnode = (GstValidateMediaStreamNode *) tmp->data; + + if (streamnode->pad == pad) { + goto done; + } + } + + snode = g_slice_new0 (GstValidateMediaStreamNode); + snode->frames = NULL; + snode->cframe = NULL; + + snode->caps = gst_caps_ref (caps); + snode->pad = gst_object_ref (pad); + + capsstr = gst_caps_to_string (caps); + padname = gst_pad_get_name (pad); + snode->str_open = + g_markup_printf_escaped + ("", padname, capsstr, 0); + + snode->str_close = g_markup_printf_escaped (""); + + ((GstValidateMediaDescriptor *) writer)->filenode->streams = + g_list_prepend (((GstValidateMediaDescriptor *) writer)-> + filenode->streams, snode); + +done: + if (caps != NULL) + gst_caps_unref (caps); + g_free (capsstr); + g_free (padname); + + return ret; +} + +gboolean + gst_validate_media_descriptor_writer_add_taglist + (GstValidateMediaDescriptorWriter * writer, const GstTagList * taglist) { + gchar *str_str = NULL; + GstValidateMediaTagsNode *tagsnode; + GstValidateMediaTagNode *tagnode; + GList *tmptag; + + g_return_val_if_fail (GST_IS_VALIDATE_MEDIA_DESCRIPTOR_WRITER (writer), + FALSE); + g_return_val_if_fail (((GstValidateMediaDescriptor *) writer)->filenode, + FALSE); + + if (((GstValidateMediaDescriptor *) writer)->filenode->tags == NULL) { + tagsnode = g_slice_new0 (GstValidateMediaTagsNode); + tagsnode->str_open = g_markup_printf_escaped (""); + tagsnode->str_close = g_markup_printf_escaped (""); + ((GstValidateMediaDescriptor *) writer)->filenode->tags = tagsnode; + } else { + tagsnode = ((GstValidateMediaDescriptor *) writer)->filenode->tags; + for (tmptag = tagsnode->tags; tmptag; tmptag = tmptag->next) { + if (gst_validate_tag_node_compare ((GstValidateMediaTagNode *) + tmptag->data, taglist)) { + GST_DEBUG ("Tag already in... not adding again %" GST_PTR_FORMAT, + taglist); + return TRUE; + } + } + } + + tagnode = g_slice_new0 (GstValidateMediaTagNode); + tagnode->taglist = gst_tag_list_copy (taglist); + str_str = gst_tag_list_to_string (tagnode->taglist); + tagnode->str_open = + g_markup_printf_escaped ("", str_str); + tagsnode->tags = g_list_prepend (tagsnode->tags, tagnode); + + g_free (str_str); + + return FALSE; +} + +gboolean +gst_validate_media_descriptor_writer_add_frame (GstValidateMediaDescriptorWriter + * writer, GstPad * pad, GstBuffer * buf) +{ + GstValidateMediaStreamNode *streamnode; + GstMapInfo map; + gchar *checksum; + guint id; + GstSegment *segment; + GstValidateMediaFrameNode *fnode; + GstValidateMediaFileNode *filenode; + + g_return_val_if_fail (GST_IS_VALIDATE_MEDIA_DESCRIPTOR_WRITER (writer), + FALSE); + g_return_val_if_fail (((GstValidateMediaDescriptor *) writer)->filenode, + FALSE); + + filenode = ((GstValidateMediaDescriptor *) writer)->filenode; + filenode->frame_detection = TRUE; + filenode->skip_parsers = + FLAG_IS_SET (writer, + GST_VALIDATE_MEDIA_DESCRIPTOR_WRITER_FLAGS_NO_PARSER); + GST_VALIDATE_MEDIA_DESCRIPTOR_LOCK (writer); + streamnode = + gst_validate_media_descriptor_find_stream_node_by_pad ( + (GstValidateMediaDescriptor *) + writer, pad); + if (streamnode == NULL) { + GST_VALIDATE_MEDIA_DESCRIPTOR_UNLOCK (writer); + return FALSE; + } + + id = g_list_length (streamnode->frames); + fnode = g_slice_new0 (GstValidateMediaFrameNode); + + g_assert (gst_buffer_map (buf, &map, GST_MAP_READ)); + checksum = g_compute_checksum_for_data (G_CHECKSUM_MD5, + (const guchar *) map.data, map.size); + gst_buffer_unmap (buf, &map); + + fnode->id = id; + fnode->offset = GST_BUFFER_OFFSET (buf); + fnode->offset_end = GST_BUFFER_OFFSET_END (buf); + fnode->duration = GST_BUFFER_DURATION (buf); + fnode->pts = GST_BUFFER_PTS (buf); + fnode->dts = GST_BUFFER_DTS (buf); + + g_assert (streamnode->segments); + segment = &((GstValidateSegmentNode *) streamnode->segments->data)->segment; + fnode->running_time = + gst_segment_to_running_time (segment, GST_FORMAT_TIME, + GST_BUFFER_PTS (buf)); + fnode->is_keyframe = + (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT) == FALSE); + + fnode->str_open = + g_markup_printf_escaped (" ", + fnode->duration, id, fnode->is_keyframe ? "true" : "false", + fnode->offset, fnode->offset_end, fnode->pts, fnode->dts, + fnode->running_time, checksum); + + fnode->str_close = NULL; + + streamnode->frames = g_list_append (streamnode->frames, fnode); + + g_free (checksum); + GST_VALIDATE_MEDIA_DESCRIPTOR_UNLOCK (writer); + + return TRUE; +} + +gboolean +gst_validate_media_descriptor_writer_write (GstValidateMediaDescriptorWriter * + writer, const gchar * filename) +{ + gboolean ret = FALSE; + gchar *serialized; + + g_return_val_if_fail (GST_IS_VALIDATE_MEDIA_DESCRIPTOR_WRITER (writer), + FALSE); + g_return_val_if_fail (((GstValidateMediaDescriptor *) writer)->filenode, + FALSE); + + serialized = serialize_filenode (writer); + + + if (g_file_set_contents (filename, serialized, -1, NULL) == TRUE) + ret = TRUE; + + + g_free (serialized); + + return ret; +} + +gchar * +gst_validate_media_descriptor_writer_serialize (GstValidateMediaDescriptorWriter + * writer) +{ + g_return_val_if_fail (GST_IS_VALIDATE_MEDIA_DESCRIPTOR_WRITER (writer), + FALSE); + g_return_val_if_fail (((GstValidateMediaDescriptor *) writer)->filenode, + FALSE); + + return serialize_filenode (writer); +} diff --git a/validate/gst/validate/media-descriptor-writer.h b/validate/gst/validate/media-descriptor-writer.h new file mode 100644 index 0000000000..334568b7c4 --- /dev/null +++ b/validate/gst/validate/media-descriptor-writer.h @@ -0,0 +1,113 @@ +/* GstValidate + * + * Copyright (c) 2012, Collabora Ltd + * Author: Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef GST_VALIDATE_MEDIA_DESCRIPTOR_WRITER_h +#define GST_VALIDATE_MEDIA_DESCRIPTOR_WRITER_h + +#include +#include +#include +#include +#include "media-descriptor.h" + +G_BEGIN_DECLS + +GST_VALIDATE_API +GType gst_validate_media_descriptor_writer_get_type (void); + +#ifndef __GI_SCANNER__ +#define GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR_WRITER (gst_validate_media_descriptor_writer_get_type ()) +#define GST_VALIDATE_MEDIA_DESCRIPTOR_WRITER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR_WRITER, GstValidateMediaDescriptorWriter)) +#define GST_VALIDATE_MEDIA_DESCRIPTOR_WRITER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR_WRITER, GstValidateMediaDescriptorWriterClass)) +#define GST_IS_VALIDATE_MEDIA_DESCRIPTOR_WRITER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR_WRITER)) +#define GST_IS_VALIDATE_MEDIA_DESCRIPTOR_WRITER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR_WRITER)) +#define GST_VALIDATE_MEDIA_DESCRIPTOR_WRITER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR_WRITER, GstValidateMediaDescriptorWriterClass)) +#endif + +typedef struct _GstValidateMediaDescriptorWriterPrivate GstValidateMediaDescriptorWriterPrivate; + + +typedef struct { + GstValidateMediaDescriptor parent; + + GstValidateMediaDescriptorWriterPrivate *priv; + +} GstValidateMediaDescriptorWriter; + +typedef struct { + + GstValidateMediaDescriptorClass parent; + +} GstValidateMediaDescriptorWriterClass; + +/** + * GstValidateMediaDescriptorWriterFlags + */ +typedef enum +{ + GST_VALIDATE_MEDIA_DESCRIPTOR_WRITER_FLAGS_NONE = 1 << 0, + GST_VALIDATE_MEDIA_DESCRIPTOR_WRITER_FLAGS_NO_PARSER = 1 << 1, + GST_VALIDATE_MEDIA_DESCRIPTOR_WRITER_FLAGS_FULL = 1 << 2, + GST_VALIDATE_MEDIA_DESCRIPTOR_WRITER_FLAGS_HANDLE_GLOGS = 1 << 3, +} GstValidateMediaDescriptorWriterFlags; + +GST_VALIDATE_API +GstValidateMediaDescriptorWriter * gst_validate_media_descriptor_writer_new_discover (GstValidateRunner *runner, + const gchar *uri, + GstValidateMediaDescriptorWriterFlags flags, + GError **err); + +GST_VALIDATE_API +GstValidateMediaDescriptorWriter * gst_validate_media_descriptor_writer_new (GstValidateRunner *runner, + const gchar *location, + GstClockTime duration, + gboolean seekable); + +gchar * gst_validate_media_descriptor_writer_get_xml_path (GstValidateMediaDescriptorWriter *writer); + +gboolean gst_validate_media_descriptor_writer_detects_frames (GstValidateMediaDescriptorWriter *writer); +GstClockTime gst_validate_media_descriptor_writer_get_duration (GstValidateMediaDescriptorWriter *writer); +gboolean gst_validate_media_descriptor_writer_get_seekable (GstValidateMediaDescriptorWriter * writer); + +GST_VALIDATE_API +gboolean gst_validate_media_descriptor_writer_add_pad (GstValidateMediaDescriptorWriter *writer, + GstPad *pad); +GST_VALIDATE_API +gboolean gst_validate_media_descriptor_writer_add_taglist (GstValidateMediaDescriptorWriter *writer, + const GstTagList *taglist); +GST_VALIDATE_API +gboolean gst_validate_media_descriptor_writer_add_frame (GstValidateMediaDescriptorWriter *writer, + GstPad *pad, + GstBuffer *buf); +GST_VALIDATE_API +gboolean gst_validate_media_descriptor_writer_add_tags (GstValidateMediaDescriptorWriter *writer, + const gchar *stream_id, + const GstTagList *taglist); +GST_VALIDATE_API +gboolean gst_validate_media_descriptor_writer_write (GstValidateMediaDescriptorWriter * writer, + const gchar * filename); +GST_VALIDATE_API +gchar * gst_validate_media_descriptor_writer_serialize (GstValidateMediaDescriptorWriter *writer); + + +G_END_DECLS + +#endif /* GST_VALIDATE_MEDIA_DESCRIPTOR_WRITER_h */ diff --git a/validate/gst/validate/media-descriptor.c b/validate/gst/validate/media-descriptor.c new file mode 100644 index 0000000000..100fdc3b4b --- /dev/null +++ b/validate/gst/validate/media-descriptor.c @@ -0,0 +1,732 @@ +/** + * Gstreamer + * + * Copyright (c) 2012, Collabora Ltd. + * Author: Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include "media-descriptor.h" + +struct _GstValidateMediaDescriptorPrivate +{ + gpointer dummy; +}; + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstValidateMediaDescriptor, + gst_validate_media_descriptor, GST_TYPE_OBJECT, + G_ADD_PRIVATE (GstValidateMediaDescriptor) + G_IMPLEMENT_INTERFACE (GST_TYPE_VALIDATE_REPORTER, NULL)); + +static inline void +free_tagnode (GstValidateMediaTagNode * tagnode) +{ + g_free (tagnode->str_open); + g_free (tagnode->str_close); + if (tagnode->taglist) + gst_tag_list_unref (tagnode->taglist); + + g_slice_free (GstValidateMediaTagNode, tagnode); +} + +static inline void +free_tagsnode (GstValidateMediaTagsNode * tagsnode) +{ + g_free (tagsnode->str_open); + g_free (tagsnode->str_close); + g_list_free_full (tagsnode->tags, (GDestroyNotify) free_tagnode); + g_slice_free (GstValidateMediaTagsNode, tagsnode); +} + +static inline void +free_framenode (GstValidateMediaFrameNode * framenode) +{ + g_free (framenode->str_open); + g_free (framenode->str_close); + + if (framenode->buf) + gst_buffer_unref (framenode->buf); + + g_slice_free (GstValidateMediaFrameNode, framenode); +} + +static inline void +free_segmentnode (GstValidateSegmentNode * segmentnode) +{ + g_free (segmentnode->str_open); + g_free (segmentnode->str_close); + + g_slice_free (GstValidateSegmentNode, segmentnode); +} + +static inline void +free_streamnode (GstValidateMediaStreamNode * streamnode) +{ + if (streamnode->caps) + gst_caps_unref (streamnode->caps); + + g_list_free_full (streamnode->frames, (GDestroyNotify) free_framenode); + g_list_free_full (streamnode->segments, (GDestroyNotify) free_segmentnode); + + if (streamnode->pad) + gst_object_unref (streamnode->pad); + + if (streamnode->tags) + free_tagsnode (streamnode->tags); + + g_free (streamnode->padname); + g_free (streamnode->id); + g_free (streamnode->str_open); + g_free (streamnode->str_close); + g_slice_free (GstValidateMediaStreamNode, streamnode); +} + +void +gst_validate_filenode_free (GstValidateMediaFileNode * filenode) +{ + g_list_free_full (filenode->streams, (GDestroyNotify) free_streamnode); + if (filenode->tags) + free_tagsnode (filenode->tags); + + g_free (filenode->uri); + + if (filenode->caps) + gst_caps_unref (filenode->caps); + + g_free (filenode->str_open); + g_free (filenode->str_close); + + g_slice_free (GstValidateMediaFileNode, filenode); +} + +gboolean + gst_validate_tag_node_compare + (GstValidateMediaTagNode * tnode, const GstTagList * tlist) +{ + if (gst_structure_is_equal (GST_STRUCTURE (tlist), + GST_STRUCTURE (tnode->taglist)) == FALSE) { + return FALSE; + } + + tnode->found = TRUE; + + return TRUE; +} + +enum +{ + PROP_0, + PROP_RUNNER, + PROP_LAST +}; + + +static void +gst_validate_media_descriptor_dispose (GstValidateMediaDescriptor * self) +{ + G_OBJECT_CLASS (gst_validate_media_descriptor_parent_class)->dispose (G_OBJECT + (self)); +} + +static void +gst_validate_media_descriptor_finalize (GstValidateMediaDescriptor * self) +{ + if (self->filenode) + gst_validate_filenode_free (self->filenode); + + G_OBJECT_CLASS (gst_validate_media_descriptor_parent_class)->finalize + (G_OBJECT (self)); +} + +static void +gst_validate_media_descriptor_init (GstValidateMediaDescriptor * self) +{ + self->filenode = g_slice_new0 (GstValidateMediaFileNode); +} + +static void +_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + switch (prop_id) { + case PROP_RUNNER: + /* we assume the runner is valid as long as this scenario is, + * no ref taken */ + gst_validate_reporter_set_runner (GST_VALIDATE_REPORTER (object), + g_value_get_object (value)); + break; + default: + break; + } +} + +static void +_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + switch (prop_id) { + case PROP_RUNNER: + /* we assume the runner is valid as long as this scenario is, + * no ref taken */ + g_value_take_object (value, + gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (object))); + break; + default: + break; + } +} + +static void +gst_validate_media_descriptor_class_init (GstValidateMediaDescriptorClass * + self_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (self_class); + + object_class->dispose = + (void (*)(GObject * object)) gst_validate_media_descriptor_dispose; + object_class->finalize = + (void (*)(GObject * object)) gst_validate_media_descriptor_finalize; + + object_class->get_property = _get_property; + object_class->set_property = _set_property; + + g_object_class_install_property (object_class, PROP_RUNNER, + g_param_spec_object ("validate-runner", "VALIDATE Runner", + "The Validate runner to report errors to", + GST_TYPE_VALIDATE_RUNNER, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); +} + +static gint +compare_tags (GstValidateMediaDescriptor * ref, + GstValidateMediaStreamNode * rstream, GstValidateMediaStreamNode * cstream) +{ + gboolean found; + GstValidateMediaTagNode *rtag, *ctag; + GList *rtag_list, *ctag_list; + GstValidateMediaTagsNode *rtags, *ctags; + + rtags = rstream->tags; + ctags = cstream->tags; + if (!rtags && !ctags) + return 1; + else if (!rtags && ctags) { + GList *taglist; + GString *all_tags = g_string_new (NULL); + + for (taglist = ctags->tags; taglist; taglist = taglist->next) { + gchar *stags = gst_tag_list_to_string (((GstValidateMediaTagNode *) + taglist->data)->taglist); + + g_string_append_printf (all_tags, "%s\n", stags); + g_free (stags); + } + + GST_VALIDATE_REPORT (ref, FILE_TAG_DETECTION_INCORRECT, + "Reference descriptor for stream %s has NO tags" + " but tags found: %s", rstream->id, all_tags->str); + + g_string_free (all_tags, TRUE); + + return 0; + } else if (rtags && !ctags) { + GList *taglist; + GString *all_tags = g_string_new (NULL); + + for (taglist = rtags->tags; taglist; taglist = taglist->next) { + gchar *stags = gst_tag_list_to_string (((GstValidateMediaTagNode *) + taglist->data)->taglist); + + g_string_append_printf (all_tags, "%s\n", stags); + g_free (stags); + } + + GST_VALIDATE_REPORT (ref, FILE_TAG_DETECTION_INCORRECT, + "Reference descriptor for stream %s has tags:\n %s\n" + " but NO tags found on the stream", rstream->id, all_tags->str); + + g_string_free (all_tags, TRUE); + return 0; + } + + for (rtag_list = rtags->tags; rtag_list; rtag_list = rtag_list->next) { + rtag = rtag_list->data; + found = FALSE; + for (ctag_list = ctags->tags; ctag_list; ctag_list = ctag_list->next) { + ctag = ctag_list->data; + if (gst_tag_list_is_equal (rtag->taglist, ctag->taglist)) { + found = TRUE; + + break; + } + } + + if (found == FALSE) { + gchar *rtaglist = gst_tag_list_to_string (rtag->taglist); + + GST_VALIDATE_REPORT (ref, FILE_TAG_DETECTION_INCORRECT, + "Reference descriptor for stream %s has tags %s" + " but no equivalent taglist was found on the compared stream", + rstream->id, rtaglist); + g_free (rtaglist); + + return 0; + } + } + + return 1; +} + +/* Workaround false warning caused by differnet file path */ +static gboolean +stream_id_is_equal (const gchar * uri, const gchar * rid, const gchar * cid) +{ + GChecksum *cs; + const gchar *stream_id; + + /* Simple case it's the same */ + if (g_strcmp0 (rid, cid) == 0) + return TRUE; + + /* If it's not from file or from our local http server, it should have been the same */ + if (!g_str_has_prefix (uri, "file://") + && !g_str_has_prefix (uri, "imagesequence:/") + && !g_str_has_prefix (uri, "http://127.0.0.1")) + return FALSE; + + /* taken from basesrc, compute the reference stream-id */ + cs = g_checksum_new (G_CHECKSUM_SHA256); + g_checksum_update (cs, (const guchar *) uri, strlen (uri)); + + stream_id = g_checksum_get_string (cs); + + /* If the reference stream_id is the URI SHA256, that means we have a single + * stream file (no demuxing), just assume it's the same id */ + if (g_strcmp0 (rid, stream_id) == 0) { + g_checksum_free (cs); + return TRUE; + } + + /* It should always be prefixed with the SHA256, otherwise it likely means + * that basesrc is no longer using a SHA256 checksum on the URI, and this + * workaround will need to be fixed */ + if (!g_str_has_prefix (rid, stream_id)) { + g_checksum_free (cs); + return FALSE; + } + g_checksum_free (cs); + + /* we strip the IDS to the delimitor, and then compare */ + rid = strchr (rid, '/'); + cid = strchr (cid, '/'); + + if (rid == NULL || cid == NULL) + return FALSE; + + if (g_strcmp0 (rid, cid) == 0) + return TRUE; + + return FALSE; +} + +static gboolean +compare_segments (GstValidateMediaDescriptor * ref, + gint i, + GstValidateMediaStreamNode * rstream, + GstValidateSegmentNode * rsegment, GstValidateSegmentNode * csegment) +{ + if (rsegment->next_frame_id != csegment->next_frame_id) { + GST_VALIDATE_REPORT (ref, FILE_SEGMENT_INCORRECT, + "Segment %" GST_SEGMENT_FORMAT + " didn't come before the same frame ID, expected to come before %d, came before %d", + &rsegment->segment, rsegment->next_frame_id, csegment->next_frame_id); + return FALSE; + } +#define CHECK_SEGMENT_FIELD(fieldname, format) \ + if (rsegment->segment.fieldname != csegment->segment.fieldname) { \ + GST_ERROR ("Expected: %" GST_SEGMENT_FORMAT " got: %" GST_SEGMENT_FORMAT, \ + &rsegment->segment, &csegment->segment); \ + GST_VALIDATE_REPORT (ref, FILE_SEGMENT_INCORRECT, \ + "Stream %s segment %d has " #fieldname \ + " mismatch, Expected " format " got: " format , \ + rstream->id, i, rsegment->segment.fieldname, \ + csegment->segment.fieldname); \ + return FALSE; \ + } + + CHECK_SEGMENT_FIELD (flags, "%d"); + CHECK_SEGMENT_FIELD (rate, "%f"); + CHECK_SEGMENT_FIELD (applied_rate, "%f"); + CHECK_SEGMENT_FIELD (base, "%" G_GUINT64_FORMAT); + CHECK_SEGMENT_FIELD (offset, "%" G_GUINT64_FORMAT); + CHECK_SEGMENT_FIELD (start, "%" G_GUINT64_FORMAT); + CHECK_SEGMENT_FIELD (stop, "%" G_GUINT64_FORMAT); + CHECK_SEGMENT_FIELD (time, "%" G_GUINT64_FORMAT); + /* We do not compare segment position since it's a field for usage only within the element */ + /* CHECK_SEGMENT_FIELD (position, "%" G_GUINT64_FORMAT); */ + CHECK_SEGMENT_FIELD (duration, "%" G_GUINT64_FORMAT); + + return TRUE; +} + +static void +append_segment_diff (GString * diff, char diffsign, GList * segments) +{ + GList *tmp; + + for (tmp = segments; tmp; tmp = tmp->next) { + gchar *ssegment = + gst_info_strdup_printf ("%c %" GST_SEGMENT_FORMAT "\n", diffsign, + &((GstValidateSegmentNode *) tmp->data)->segment); + g_string_append (diff, ssegment); + g_free (ssegment); + + } +} + +static gboolean +compare_segment_list (GstValidateMediaDescriptor * ref, + GstValidateMediaStreamNode * rstream, GstValidateMediaStreamNode * cstream) +{ + gint i; + GList *rsegments, *csegments; + + /* Keep compatibility with media stream files that do not have segments */ + if (rstream->segments + && g_list_length (rstream->segments) != + g_list_length (cstream->segments)) { + GString *diff = g_string_new (NULL); + + append_segment_diff (diff, '-', rstream->segments); + append_segment_diff (diff, '+', cstream->segments); + GST_VALIDATE_REPORT (ref, FILE_SEGMENT_INCORRECT, + "Stream reference has %i segments, compared one has %i segments\n%s", + g_list_length (rstream->segments), g_list_length (cstream->segments), + diff->str); + g_string_free (diff, TRUE); + } + + for (i = 0, rsegments = rstream->segments, csegments = cstream->segments; + rsegments; + rsegments = rsegments->next, csegments = csegments->next, i++) { + GstValidateSegmentNode *rsegment, *csegment; + + if (csegments == NULL) { + /* The list was checked to be of the same size */ + g_assert_not_reached (); + return FALSE; + } + + rsegment = rsegments->data; + csegment = csegments->data; + + if (!compare_segments (ref, i, rstream, rsegment, csegment)) + return FALSE; + } + + return TRUE; +} + +static gboolean +compare_frames (GstValidateMediaDescriptor * ref, + GstValidateMediaStreamNode * + rstream, GstValidateMediaFrameNode * rframe, + GstValidateMediaFrameNode * cframe) +{ + if (rframe->id != cframe->id) { + GST_VALIDATE_REPORT (ref, FILE_FRAMES_INCORRECT, + "Stream frame %s ids mismatch: %" G_GUINT64_FORMAT " != %" + G_GUINT64_FORMAT, rstream->id, rframe->id, cframe->id); + return FALSE; + } +#define CHECK_FRAME_FIELD(fieldname, format, unknown_value) \ + if (rframe->fieldname != unknown_value && rframe->fieldname != cframe->fieldname) { \ + GST_VALIDATE_REPORT (ref, FILE_FRAMES_INCORRECT, \ + "Stream %s frames with id %" G_GUINT64_FORMAT " have " #fieldname \ + " mismatch. Expected " format ", got " format, rstream->id, \ + rframe->id, rframe->fieldname, cframe->fieldname); \ + return FALSE; \ + } + + CHECK_FRAME_FIELD (pts, "%" G_GUINT64_FORMAT, GST_VALIDATE_UNKNOWN_UINT64); + CHECK_FRAME_FIELD (dts, "%" G_GUINT64_FORMAT, GST_VALIDATE_UNKNOWN_UINT64); + CHECK_FRAME_FIELD (duration, "%" G_GUINT64_FORMAT, + GST_VALIDATE_UNKNOWN_UINT64); + CHECK_FRAME_FIELD (running_time, "%" G_GUINT64_FORMAT, + GST_VALIDATE_UNKNOWN_UINT64); + CHECK_FRAME_FIELD (is_keyframe, "%d", GST_VALIDATE_UNKNOWN_BOOL); + + return TRUE; +} + +static gboolean +compare_frames_list (GstValidateMediaDescriptor * ref, + GstValidateMediaStreamNode * rstream, GstValidateMediaStreamNode * cstream) +{ + GList *rframes, *cframes; + + if (g_list_length (rstream->frames) != g_list_length (cstream->frames)) { + GST_VALIDATE_REPORT (ref, FILE_FRAMES_INCORRECT, + "Stream reference has %i frames, compared one has %i frames", + g_list_length (rstream->frames), g_list_length (cstream->frames)); + return FALSE; + } + + for (rframes = rstream->frames, cframes = cstream->frames; rframes; + rframes = g_list_next (rframes), cframes = g_list_next (cframes)) { + GstValidateMediaFrameNode *rframe, *cframe; + + if (cframes == NULL) { + /* The list was checked to be of the same size */ + g_assert_not_reached (); + return FALSE; + } + + rframe = rframes->data; + cframe = cframes->data; + + if (!compare_frames (ref, rstream, rframe, cframe)) { + return FALSE; + } + } + + return TRUE; +} + +static GstCaps * +caps_cleanup_parsing_fields (GstCaps * caps) +{ + gint i; + GstCaps *res = gst_caps_copy (caps); + + for (i = 0; i < gst_caps_get_size (res); i++) { + GstStructure *s = gst_caps_get_structure (res, i); + + gst_structure_remove_fields (s, "stream-format", "codec_data", "parsed", + "frames", "alignment", NULL); + } + + return res; +} + +/* Return TRUE if found FALSE otherwise */ +static gboolean +compare_streams (GstValidateMediaDescriptor * ref, + GstValidateMediaStreamNode * rstream, GstValidateMediaStreamNode * cstream) +{ + GstCaps *rcaps, *ccaps; + + if (!stream_id_is_equal (ref->filenode->uri, rstream->id, cstream->id)) + return FALSE; + + rcaps = caps_cleanup_parsing_fields (rstream->caps); + ccaps = caps_cleanup_parsing_fields (cstream->caps); + + if (!gst_caps_is_equal (rcaps, ccaps)) { + gchar *rcaps_str = gst_caps_to_string (rcaps), + *ccaps_str = gst_caps_to_string (ccaps); + GST_VALIDATE_REPORT (ref, FILE_PROFILE_INCORRECT, + "Reference descriptor for stream %s has caps: %s" + " but compared stream %s has caps: %s", + rstream->id, rcaps_str, cstream->id, ccaps_str); + g_free (rcaps_str); + g_free (ccaps_str); + } + + gst_caps_unref (rcaps); + gst_caps_unref (ccaps); + /* We ignore the return value on purpose as this is not critical */ + compare_tags (ref, rstream, cstream); + + compare_segment_list (ref, rstream, cstream); + compare_frames_list (ref, rstream, cstream); + + return TRUE; +} + +gboolean +gst_validate_media_descriptors_compare (GstValidateMediaDescriptor * ref, + GstValidateMediaDescriptor * compared) +{ + GList *rstream_list; + GstValidateMediaFileNode + * rfilenode = ref->filenode, *cfilenode = compared->filenode; + + if (rfilenode->duration != cfilenode->duration) { + GST_VALIDATE_REPORT (ref, FILE_DURATION_INCORRECT, + "Duration %" GST_TIME_FORMAT " is different from the reference %" + GST_TIME_FORMAT, GST_TIME_ARGS (cfilenode->duration), + GST_TIME_ARGS (rfilenode->duration)); + } + + if (rfilenode->seekable != cfilenode->seekable) { + GST_VALIDATE_REPORT (ref, FILE_SEEKABLE_INCORRECT, + "File known as %s but is reported %s now", + rfilenode->seekable ? "seekable" : "not seekable", + cfilenode->seekable ? "seekable" : "not seekable"); + } + + if (g_list_length (rfilenode->streams) != g_list_length (cfilenode->streams)) { + GST_VALIDATE_REPORT (ref, FILE_PROFILE_INCORRECT, + "Reference descriptor has %i streams != compared which has %i streams", + g_list_length (rfilenode->streams), g_list_length (cfilenode->streams)); + + return FALSE; + } + + + for (rstream_list = rfilenode->streams; rstream_list; + rstream_list = rstream_list->next) { + GList *cstream_list; + gboolean sfound = FALSE; + + for (cstream_list = cfilenode->streams; cstream_list; + cstream_list = cstream_list->next) { + + sfound = compare_streams (ref, rstream_list->data, cstream_list->data); + if (sfound) + break; + } + + if (!sfound) { + GST_VALIDATE_REPORT (ref, FILE_PROFILE_INCORRECT, + "Could not find stream %s in the compared descriptor", + ((GstValidateMediaStreamNode *) rstream_list->data)->id); + } + } + + return TRUE; +} + +gboolean +gst_validate_media_descriptor_detects_frames (GstValidateMediaDescriptor * self) +{ + g_return_val_if_fail (GST_IS_VALIDATE_MEDIA_DESCRIPTOR (self), FALSE); + g_return_val_if_fail (self->filenode, FALSE); + + return self->filenode->frame_detection; +} + +/** + * gst_validate_media_descriptor_get_buffers: (skip): + */ +gboolean +gst_validate_media_descriptor_get_buffers (GstValidateMediaDescriptor * self, + GstPad * pad, GCompareFunc compare_func, GList ** bufs) +{ + GList *tmpstream, *tmpframe; + gboolean check = (pad == NULL), ret = FALSE; + GstCaps *pad_caps = gst_pad_get_current_caps (pad); + + g_return_val_if_fail (GST_IS_VALIDATE_MEDIA_DESCRIPTOR (self), FALSE); + g_return_val_if_fail (self->filenode, FALSE); + + for (tmpstream = self->filenode->streams; + tmpstream; tmpstream = tmpstream->next) { + GstValidateMediaStreamNode + * streamnode = (GstValidateMediaStreamNode *) tmpstream->data; + + if (pad && streamnode->pad == pad) + check = TRUE; + + if (!streamnode->pad && gst_caps_is_subset (pad_caps, streamnode->caps)) { + check = TRUE; + } + + if (check) { + ret = TRUE; + for (tmpframe = streamnode->frames; tmpframe; tmpframe = tmpframe->next) { + if (compare_func) + *bufs = + g_list_insert_sorted (*bufs, + gst_buffer_ref (( + (GstValidateMediaFrameNode + *) tmpframe->data)->buf), compare_func); + else + *bufs = + g_list_prepend (*bufs, + gst_buffer_ref (( + (GstValidateMediaFrameNode *) tmpframe->data)->buf)); + } + + if (pad != NULL) + goto done; + } + } + + +done: + + if (compare_func == NULL) + *bufs = g_list_reverse (*bufs); + + gst_caps_unref (pad_caps); + return ret; +} + +gboolean +gst_validate_media_descriptor_has_frame_info (GstValidateMediaDescriptor * self) +{ + GList *tmpstream; + + for (tmpstream = self->filenode->streams; + tmpstream; tmpstream = tmpstream->next) { + GstValidateMediaStreamNode + * streamnode = (GstValidateMediaStreamNode *) tmpstream->data; + + if (g_list_length (streamnode->frames)) + return TRUE; + } + + return FALSE; +} + +GstClockTime +gst_validate_media_descriptor_get_duration (GstValidateMediaDescriptor * self) +{ + g_return_val_if_fail (GST_IS_VALIDATE_MEDIA_DESCRIPTOR (self), FALSE); + g_return_val_if_fail (self->filenode, FALSE); + + return self->filenode->duration; +} + +gboolean +gst_validate_media_descriptor_get_seekable (GstValidateMediaDescriptor * self) +{ + g_return_val_if_fail (GST_IS_VALIDATE_MEDIA_DESCRIPTOR (self), FALSE); + g_return_val_if_fail (self->filenode, FALSE); + + return self->filenode->seekable; +} + +/** + * gst_validate_media_descriptor_get_pads: (skip): + */ +GList * +gst_validate_media_descriptor_get_pads (GstValidateMediaDescriptor * self) +{ + GList *ret = NULL, *tmp; + + for (tmp = self->filenode->streams; tmp; tmp = tmp->next) { + GstValidateMediaStreamNode + * snode = (GstValidateMediaStreamNode *) tmp->data; + ret = g_list_append (ret, gst_pad_new (snode->padname, GST_PAD_UNKNOWN)); + } + + return ret; +} diff --git a/validate/gst/validate/media-descriptor.h b/validate/gst/validate/media-descriptor.h new file mode 100644 index 0000000000..65b3a57ac1 --- /dev/null +++ b/validate/gst/validate/media-descriptor.h @@ -0,0 +1,194 @@ +/* GstValidate + * + * Copyright (c) 2012, Collabora Ltd. + * Author: Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_VALIDATE_MEDIA_DESCRIPTOR_H__ +#define __GST_VALIDATE_MEDIA_DESCRIPTOR_H__ + +#include +#include +#include +#include "gst-validate-report.h" + +G_BEGIN_DECLS + +#define GST_VALIDATE_UNKNOWN_UINT64 (G_MAXUINT64 - 2) +#define GST_VALIDATE_UNKNOWN_BOOL (G_MAXUINT32 - 2) +typedef struct +{ + /* Children */ + /* GstValidateMediaTagNode */ + GList *tags; + + gchar *str_open; + gchar *str_close; +} GstValidateMediaTagsNode; + +/* Parsing structures */ +typedef struct +{ + /* Children */ + /* GstValidateMediaStreamNode */ + GList *streams; + /* GstValidateMediaTagsNode */ + GstValidateMediaTagsNode *tags; + + /* attributes */ + guint64 id; + gchar *uri; + GstClockTime duration; + gboolean frame_detection; + gboolean skip_parsers; + gboolean seekable; + + GstCaps *caps; + + gchar *str_open; + gchar *str_close; +} GstValidateMediaFileNode; + +typedef struct +{ + /* Children */ + GstTagList *taglist; + + /* Testing infos */ + gboolean found; + + gchar *str_open; + gchar *str_close; +} GstValidateMediaTagNode; + +typedef struct +{ + /* Children */ + /* GstValidateMediaFrameNode */ + GList *frames; + + /* GstValidateMediaTagsNode */ + GstValidateMediaTagsNode *tags; + + /* Attributes */ + GstCaps *caps; + GList * segments; + gchar *id; + gchar *padname; + + /* Testing infos */ + GstPad *pad; + GList *cframe; + + gchar *str_open; + gchar *str_close; +} GstValidateMediaStreamNode; + +typedef struct +{ + /* Attributes */ + guint64 id; + guint64 offset; + guint64 offset_end; + GstClockTime duration; + GstClockTime pts, dts; + GstClockTime running_time; + gboolean is_keyframe; + + GstBuffer *buf; + + gchar *checksum; + gchar *str_open; + gchar *str_close; +} GstValidateMediaFrameNode; + +typedef struct +{ + gint next_frame_id; + + GstSegment segment; + + gchar *str_open; + gchar *str_close; +} GstValidateSegmentNode; + +GST_VALIDATE_API +void gst_validate_filenode_free (GstValidateMediaFileNode * + filenode); +GST_VALIDATE_API +gboolean gst_validate_tag_node_compare (GstValidateMediaTagNode * + tnode, const GstTagList * tlist); + +GST_VALIDATE_API +GType gst_validate_media_descriptor_get_type (void); + +#ifndef __GI_SCANNER__ +#define GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR (gst_validate_media_descriptor_get_type ()) +#define GST_VALIDATE_MEDIA_DESCRIPTOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR, GstValidateMediaDescriptor)) +#define GST_VALIDATE_MEDIA_DESCRIPTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR, GstValidateMediaDescriptorClass)) +#define GST_IS_VALIDATE_MEDIA_DESCRIPTOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR)) +#define GST_IS_VALIDATE_MEDIA_DESCRIPTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR)) +#define GST_VALIDATE_MEDIA_DESCRIPTOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_VALIDATE_MEDIA_DESCRIPTOR, GstValidateMediaDescriptorClass)) +#endif + +#define GST_VALIDATE_MEDIA_DESCRIPTOR_GET_LOCK(obj) (&GST_VALIDATE_MEDIA_DESCRIPTOR(obj)->lock) +#define GST_VALIDATE_MEDIA_DESCRIPTOR_LOCK(obj) g_mutex_lock(GST_VALIDATE_MEDIA_DESCRIPTOR_GET_LOCK(obj)) +#define GST_VALIDATE_MEDIA_DESCRIPTOR_UNLOCK(obj) g_mutex_unlock(GST_VALIDATE_MEDIA_DESCRIPTOR_GET_LOCK(obj)) + +typedef struct _GstValidateMediaDescriptorPrivate + GstValidateMediaDescriptorPrivate; + +typedef struct +{ + GstObject parent; + + GstValidateMediaFileNode *filenode; + + GMutex lock; + + GstValidateMediaDescriptorPrivate *priv; +} GstValidateMediaDescriptor; + +typedef struct +{ + GstObjectClass parent; + +} GstValidateMediaDescriptorClass; + +GST_VALIDATE_API +gboolean gst_validate_media_descriptors_compare (GstValidateMediaDescriptor * + ref, GstValidateMediaDescriptor * compared); +GST_VALIDATE_API gboolean +gst_validate_media_descriptor_detects_frames (GstValidateMediaDescriptor * + self); +GST_VALIDATE_API +gboolean gst_validate_media_descriptor_get_buffers (GstValidateMediaDescriptor * + self, GstPad * pad, GCompareFunc compare_func, GList ** bufs); +GST_VALIDATE_API gboolean +gst_validate_media_descriptor_has_frame_info (GstValidateMediaDescriptor * + self); +GST_VALIDATE_API GstClockTime +gst_validate_media_descriptor_get_duration (GstValidateMediaDescriptor * self); +GST_VALIDATE_API +gboolean gst_validate_media_descriptor_get_seekable (GstValidateMediaDescriptor + * self); +GST_VALIDATE_API +GList *gst_validate_media_descriptor_get_pads (GstValidateMediaDescriptor * + self); +G_END_DECLS +#endif diff --git a/validate/gst/validate/meson.build b/validate/gst/validate/meson.build new file mode 100644 index 0000000000..d04f1a4f68 --- /dev/null +++ b/validate/gst/validate/meson.build @@ -0,0 +1,117 @@ +gstvalidate_sources = [ + 'gst-validate-reporter.c', + 'gst-validate-mockdecryptor.c', + 'gst-validate-monitor.c', + 'gst-validate-element-monitor.c', + 'gst-validate-bin-monitor.c', + 'gst-validate-pipeline-monitor.c', + 'gst-validate-pad-monitor.c', + 'gst-validate-monitor-factory.c', + 'gst-validate-report.c', + 'gst-validate-scenario.c', + 'gst-validate-override.c', + 'gst-validate-utils.c', + 'gst-validate-override-registry.c', + 'media-descriptor.c', + 'media-descriptor-writer.c', + 'media-descriptor-parser.c', + 'gst-validate-media-info.c', + 'gst-validate-extra-checks.c', + 'flow/gstvalidateflow.c', + 'flow/formatting.c', + 'validate.c', +] + +gstvalidate_headers = [ + 'validate.h', + 'validate-prelude.h', + 'gst-validate-types.h', + 'gst-validate-bin-monitor.h', + 'gst-validate-pipeline-monitor.h', + 'gst-validate-element-monitor.h', + 'gst-validate-enums.h', + 'media-descriptor.h', + 'media-descriptor-writer.h', + 'media-descriptor-parser.h', + 'gst-validate-monitor-factory.h', + 'gst-validate-monitor.h', + 'gst-validate-override.h', + 'gst-validate-override-registry.h', + 'gst-validate-pad-monitor.h', + 'gst-validate-reporter.h', + 'gst-validate-report.h', + 'gst-validate-runner.h', + 'gst-validate-scenario.h', + 'gst-validate-utils.h', + 'gst-validate-media-info.h' +] + +install_headers(gstvalidate_headers, subdir : 'gstreamer-1.0/gst/validate') + +gst_validate_enums = gnome.mkenums('gstvalidateenumtypes', + sources : gstvalidate_headers, + h_template : 'gst-validate-enum-types.h.template', + c_template : 'gst-validate-enum-types.c.template', + install_header : true, + install_dir : join_paths(get_option('includedir'), 'gstreamer-1.0/gst/validate')) + +validate_deps = [gst_check_dep, gst_dep, gst_controller_dep, gstbase_dep, glib_dep, gio_dep, gmodule_dep, gst_pbutils_dep, mathlib, json_dep] +gstvalidate = library('gstvalidate-1.0', + sources: files('gst-validate-runner.c') + gstvalidate_sources + gst_validate_enums, + version : libversion, + soversion : soversion, + darwin_versions : osxversion, + include_directories : [inc_dirs], + install: true, + c_args : [gst_c_args] + ['-D_GNU_SOURCE'], + dependencies : validate_deps) + +gstvalidatetracer = library('gstvalidatetracer', + sources: files('gst-validate-runner.c') + gst_validate_enums, + include_directories : [inc_dirs], + install: true, + c_args : [gst_c_args] + ['-D__GST_VALIDATE_PLUGIN', '-D_GNU_SOURCE'], + install_dir : plugins_install_dir, + objects: gstvalidate.extract_objects(gstvalidate_sources), + dependencies : validate_deps) + +plugins += gstvalidatetracer +pkgconfig.generate(gstvalidate, + libraries : [gst_dep], + subdirs : pkgconfig_subdirs, + name : 'gst-validate-1.0', + description : 'Gstreamer Validate', +) + +validate_gen_sources = [] +if build_gir + gst_validate_gir_extra_args = gir_init_section + [ '--c-include=gst/validate/validate.h' ] + validate_gir = gnome.generate_gir(gstvalidate, + sources : gstvalidate_sources + gstvalidate_headers + gst_validate_enums, + nsversion : '1.0', + namespace : 'GstValidate', + symbol_prefix : 'gst_validate', + identifier_prefix : 'GstValidate', + export_packages : 'gst-validate-' + apiversion, + includes : ['GObject-2.0', + 'GLib-2.0', + 'Gio-2.0', + 'GModule-2.0', + 'Gst-' + apiversion, + 'GstPbutils-' + apiversion], + install : true, + dependencies : validate_deps, + extra_args : gst_validate_gir_extra_args, + ) + validate_gen_sources += [validate_gir] +endif + +validate_dep = declare_dependency(link_with : gstvalidate, + include_directories : [inc_dirs], + dependencies : validate_deps, + sources : validate_gen_sources +) + +meson.override_dependency('gst-validate-1.0', validate_dep) + +pkgconfig.generate(gstvalidatetracer, install_dir : plugins_pkgconfig_install_dir) diff --git a/validate/gst/validate/validate-prelude.h b/validate/gst/validate/validate-prelude.h new file mode 100644 index 0000000000..5163463765 --- /dev/null +++ b/validate/gst/validate/validate-prelude.h @@ -0,0 +1,31 @@ +/* GStreamer Validate Library + * Copyright (C) 2018 GStreamer developers + * + * validate-prelude.h: prelude include header for gst-validate library + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_VALIDATE_PRELUDE_H__ +#define __GST_VALIDATE_PRELUDE_H__ + +#include + +#ifndef GST_VALIDATE_API +#define GST_VALIDATE_API GST_EXPORT +#endif + +#endif /* __GST_VALIDATE_PRELUDE_H__ */ diff --git a/validate/gst/validate/validate.c b/validate/gst/validate/validate.c new file mode 100644 index 0000000000..4eea3267ba --- /dev/null +++ b/validate/gst/validate/validate.c @@ -0,0 +1,740 @@ +/* GStreamer + * + * Copyright (C) 2013 Collabora Ltd. + * Author: Thiago Sousa Santos + * + * validate.c - Validate generic functions + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/** + * SECTION:validate + * @title: Initialization + * @short_description: Initialize GstValidate + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include /* for LC_NUMERIC */ + +#include +/* For g_stat () */ +#include +#include +#include + +#include + +#include "validate.h" +#include "gst-validate-utils.h" +#include "gst-validate-internal.h" + +#ifdef G_OS_WIN32 +#define WIN32_LEAN_AND_MEAN /* prevents from including too many things */ +#include /* GetStdHandle, windows console */ +HMODULE _priv_gstvalidate_dll_handle = NULL; +#endif /* G_OS_WIN32 */ + +GST_DEBUG_CATEGORY (gstvalidate_debug); + +static GMutex _gst_validate_registry_mutex; +static GstRegistry *_gst_validate_registry_default = NULL; + +G_LOCK_DEFINE_STATIC (all_configs_lock); +static GList *all_configs = NULL; +static gboolean got_configs = FALSE; + +static GList *core_config = NULL; +static gboolean testfile_used = FALSE; +static GList *testfile_structs = NULL; +static gchar *global_testfile = NULL; +static gboolean validate_initialized = FALSE; +static gboolean loaded_globals = FALSE; +GstClockTime _priv_start_time; + +GQuark _Q_VALIDATE_MONITOR; + +#ifdef G_OS_WIN32 +BOOL WINAPI DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved); +BOOL WINAPI +DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + if (fdwReason == DLL_PROCESS_ATTACH) + _priv_gstvalidate_dll_handle = (HMODULE) hinstDLL; + + return TRUE; +} +#endif /* G_OS_WIN32 */ + +static GstRegistry * +gst_validate_registry_get (void) +{ + GstRegistry *registry; + + g_mutex_lock (&_gst_validate_registry_mutex); + if (G_UNLIKELY (!_gst_validate_registry_default)) { + _gst_validate_registry_default = g_object_new (GST_TYPE_REGISTRY, NULL); + gst_object_ref_sink (GST_OBJECT_CAST (_gst_validate_registry_default)); + } + registry = _gst_validate_registry_default; + g_mutex_unlock (&_gst_validate_registry_mutex); + + return registry; +} + +#define GST_VALIDATE_PLUGIN_CONFIG "gst-validate-plugin-config" + +static void +_free_plugin_config (gpointer data) +{ + g_list_free (data); +} + +/* Copied from gststructure.c to avoid assertion */ +static gboolean +gst_structure_validate_name (const gchar * name) +{ + const gchar *s; + + g_return_val_if_fail (name != NULL, FALSE); + + if (G_UNLIKELY (!g_ascii_isalpha (*name))) { + GST_INFO ("Invalid character '%c' at offset 0 in structure name: %s", + *name, name); + return FALSE; + } + + /* FIXME: test name string more */ + s = &name[1]; + while (*s && (g_ascii_isalnum (*s) || strchr ("/-_.:+", *s) != NULL)) + s++; + if (*s == ',') + return TRUE; + + if (G_UNLIKELY (*s != '\0')) { + GST_INFO ("Invalid character '%c' at offset %" G_GUINTPTR_FORMAT " in" + " structure name: %s", *s, ((guintptr) s - (guintptr) name), name); + return FALSE; + } + + return TRUE; +} + +static gboolean +_set_vars_func (GQuark field_id, const GValue * value, GstStructure * vars) +{ + gst_structure_id_set_value (vars, field_id, value); + + return TRUE; +} + +static GstStructure * +get_test_file_meta (void) +{ + GList *tmp; + + for (tmp = testfile_structs; tmp; tmp = tmp->next) { + if (gst_structure_has_name (tmp->data, "meta")) + return tmp->data; + } + + return NULL; +} + +static void +create_config (const gchar * config) +{ + GstStructure *local_vars; + GList *structures = NULL, *tmp; + gchar *config_file = NULL; + GFile *f; + + local_vars = gst_structure_new_empty ("vars"); + f = g_file_new_for_path (config); + if (g_file_query_exists (f, NULL)) { + structures = gst_validate_utils_structs_parse_from_filename (config, NULL, + &config_file); + } else { + GstCaps *confs = NULL; + + if (gst_structure_validate_name (config)) + confs = gst_caps_from_string (config); + + if (confs) { + gint i; + + for (i = 0; i < gst_caps_get_size (confs); i++) { + GstStructure *structure = gst_caps_get_structure (confs, i); + + structures = g_list_append (structures, gst_structure_copy (structure)); + } + + gst_caps_unref (confs); + } + } + g_object_unref (f); + gst_validate_structure_set_variables_from_struct_file (local_vars, + config_file); + g_free (config_file); + + for (tmp = structures; tmp; tmp = tmp->next) { + GstStructure *structure = tmp->data; + + if (gst_structure_has_field (structure, "set-vars")) { + gst_structure_remove_field (structure, "set-vars"); + gst_structure_foreach (structure, + (GstStructureForeachFunc) _set_vars_func, local_vars); + gst_structure_free (structure); + } else if (!loaded_globals + && gst_structure_has_name (structure, "set-globals")) { + gst_validate_structure_resolve_variables (NULL, structure, local_vars, 0); + gst_validate_set_globals (structure); + gst_structure_free (structure); + } else { + gst_validate_structure_resolve_variables (NULL, structure, local_vars, 0); + all_configs = g_list_append (all_configs, structure); + } + } + + loaded_globals = TRUE; + gst_structure_free (local_vars); + g_list_free (structures); +} + +static GList * +get_structures_from_array (GstStructure * structure, const gchar * fieldname) +{ + const GValue *value; + GList *res = NULL; + guint i, size; + + value = gst_structure_get_value (structure, fieldname); + if (!value) + return NULL; + + if (GST_VALUE_HOLDS_STRUCTURE (value)) { + return g_list_append (res, + gst_structure_copy (gst_value_get_structure (value))); + } + + if (!GST_VALUE_HOLDS_LIST (value)) { + return NULL; + } + + size = gst_value_list_get_size (value); + for (i = 0; i < size; i++) { + const GValue *v1 = gst_value_list_get_value (value, i); + + if (!GST_VALUE_HOLDS_STRUCTURE (v1)) + break; + + res = + g_list_append (res, gst_structure_copy (gst_value_get_structure (v1))); + } + + + return res; +} + +static GList * +get_structures_from_array_in_meta (const gchar * fieldname) +{ + GList *res = NULL; + gchar **strs = NULL, *filename = NULL, *debug = NULL; + gint current_lineno = -1; + GstStructure *meta = get_test_file_meta (); + + if (!meta) + return NULL; + + res = get_structures_from_array (meta, fieldname); + if (res) + return res; + + gst_structure_get (meta, + "__lineno__", G_TYPE_INT, ¤t_lineno, + "__debug__", G_TYPE_STRING, &debug, + "__filename__", G_TYPE_STRING, &filename, NULL); + strs = gst_validate_utils_get_strv (meta, fieldname); + + if (strs) { + gint i; + + for (i = 0; strs[i]; i++) { + GstStructure *tmpstruct = gst_structure_from_string (strs[i], NULL); + + if (tmpstruct == NULL) { + gst_validate_abort ("%s:%d: Invalid structure\n %4d | %s\n%s", + filename, current_lineno, current_lineno, strs[i], debug); + } + + gst_structure_set (tmpstruct, + "__lineno__", G_TYPE_INT, current_lineno, + "__filename__", G_TYPE_STRING, filename, + "__debug__", G_TYPE_STRING, debug, NULL); + res = g_list_append (res, tmpstruct); + } + } + + g_free (filename); + g_free (debug); + g_strfreev (strs); + + return res; +} + +/** + * gst_validate_plugin_get_config: + * @plugin: a #GstPlugin, or #NULL + * + * Return the configuration specific to @plugin, or the "core" one if @plugin + * is #NULL + * + * Returns: (transfer none) (element-type GstStructure): a list of #GstStructure + */ +GList * +gst_validate_plugin_get_config (GstPlugin * plugin) +{ + const gchar *suffix; + GList *plugin_conf = NULL; + + if (plugin) { + if ((plugin_conf = + g_object_get_data (G_OBJECT (plugin), GST_VALIDATE_PLUGIN_CONFIG))) + return plugin_conf; + + suffix = gst_plugin_get_name (plugin); + } else { + if (core_config) + return core_config; + + suffix = "core"; + } + + plugin_conf = gst_validate_get_config (suffix); + if (plugin) + g_object_set_data_full (G_OBJECT (plugin), GST_VALIDATE_PLUGIN_CONFIG, + plugin_conf, _free_plugin_config); + else + core_config = plugin_conf; + + return plugin_conf; +} + +static void +gst_validate_ensure_all_configs (void) +{ + GStrv tmp; + gint i; + const gchar *config; + + if (got_configs) + return; + + got_configs = TRUE; + all_configs = get_structures_from_array_in_meta ("configs"); + config = g_getenv ("GST_VALIDATE_CONFIG"); + if (!config) + return; + + tmp = g_strsplit (config, G_SEARCHPATH_SEPARATOR_S, -1); + for (i = 0; tmp[i] != NULL; i++) { + if (tmp[i][0] == '\0') + continue; + + create_config (tmp[i]); + } + g_strfreev (tmp); +} + +GList * +gst_validate_get_config (const gchar * structname) +{ + GList *tmp, *res = NULL; + + G_LOCK (all_configs_lock); + gst_validate_ensure_all_configs (); + + for (tmp = all_configs; tmp; tmp = tmp->next) { + gint n_usages = 0; + + if (structname && !gst_structure_has_name (tmp->data, structname)) { + continue; + } else if (structname) { + gst_structure_get (tmp->data, "__n_usages__", G_TYPE_INT, &n_usages, + NULL); + n_usages++; + gst_structure_set (tmp->data, "__n_usages__", G_TYPE_INT, n_usages, NULL); + } + res = g_list_append (res, tmp->data); + } + G_UNLOCK (all_configs_lock); + + return res; +} + +static void +gst_validate_init_plugins (void) +{ + GstRegistry *registry; + const gchar *plugin_path; + + gst_registry_fork_set_enabled (FALSE); + registry = gst_validate_registry_get (); + + plugin_path = g_getenv ("GST_VALIDATE_PLUGIN_PATH"); + if (plugin_path) { + char **list; + int i; + + GST_DEBUG ("GST_VALIDATE_PLUGIN_PATH set to %s", plugin_path); + list = g_strsplit (plugin_path, G_SEARCHPATH_SEPARATOR_S, 0); + for (i = 0; list[i]; i++) { + gst_registry_scan_path (registry, list[i]); + } + g_strfreev (list); + } else { + GST_DEBUG ("GST_VALIDATE_PLUGIN_PATH not set"); + } + + if (plugin_path == NULL) { + char *home_plugins; + + /* plugins in the user's home directory take precedence over + * system-installed ones */ + home_plugins = g_build_filename (g_get_user_data_dir (), + "gstreamer-" GST_API_VERSION, "plugins", NULL); + + GST_DEBUG ("scanning home plugins %s", home_plugins); + gst_registry_scan_path (registry, home_plugins); + g_free (home_plugins); + + /* add the main (installed) library path */ + +#ifdef G_OS_WIN32 + { + char *base_dir; + char *dir; + + base_dir = + g_win32_get_package_installation_directory_of_module + (_priv_gstvalidate_dll_handle); + + dir = g_build_filename (base_dir, + "lib", "gstreamer-" GST_API_VERSION, "validate", NULL); + + GST_DEBUG ("scanning DLL dir %s", dir); + + gst_registry_scan_path (registry, dir); + + g_free (dir); + g_free (base_dir); + } +#else + gst_registry_scan_path (registry, VALIDATEPLUGINDIR); +#endif + } + gst_registry_fork_set_enabled (TRUE); +} + +void +gst_validate_init_debug (void) +{ + GST_DEBUG_CATEGORY_INIT (gstvalidate_debug, "validate", 0, + "Validation library"); +} + +/** + * gst_validate_init: + * + * Initializes GstValidate. Call this before any usage of GstValidate. + * You should take care of initializing GStreamer before calling this + * function. + */ +void +gst_validate_init (void) +{ + if (validate_initialized) { + return; + } + gst_validate_init_debug (); + _priv_start_time = gst_util_get_timestamp (); + _Q_VALIDATE_MONITOR = g_quark_from_static_string ("validate-monitor"); + + setlocale (LC_NUMERIC, "C"); + + /* init the report system (can be called multiple times) */ + gst_validate_report_init (); + + /* Init the scenario system */ + init_scenarios (); + + /* Ensure we load overrides before any use of a monitor */ + gst_validate_override_registry_preload (); + + validate_initialized = TRUE; + + gst_validate_extra_checks_init (); + gst_validate_flow_init (); + gst_validate_init_plugins (); + gst_validate_init_runner (); +} + +void +gst_validate_deinit (void) +{ + g_mutex_lock (&_gst_validate_registry_mutex); + + g_list_free (core_config); + core_config = NULL; + + g_list_free_full (all_configs, (GDestroyNotify) gst_structure_free); + gst_validate_deinit_runner (); + + gst_validate_scenario_deinit (); + + g_clear_object (&_gst_validate_registry_default); + + g_list_free_full (testfile_structs, (GDestroyNotify) gst_structure_free); + testfile_structs = NULL; + g_clear_pointer (&global_testfile, g_free); + + _priv_validate_override_registry_deinit (); + validate_initialized = FALSE; + gst_validate_report_deinit (); + + g_mutex_unlock (&_gst_validate_registry_mutex); + g_mutex_clear (&_gst_validate_registry_mutex); +} + +gboolean +gst_validate_is_initialized (void) +{ + return validate_initialized; +} + +GList * +gst_validate_get_test_file_expected_issues (void) +{ + GList *res = get_structures_from_array_in_meta ("expected-issues"), *tmp; + + for (tmp = res; tmp; tmp = tmp->next) { + GstStructure *known_issue = tmp->data; + const gchar *summary = gst_structure_get_string (known_issue, "summary"); + const gchar *id = gst_structure_get_string (known_issue, "issue-id"); + + if (!id && !summary) + gst_validate_error_structure (known_issue, + "Missing 'summary' or 'issue-id' fields."); + } + + return res; +} + +gboolean +gst_validate_get_test_file_scenario (GList ** structs, + const gchar ** scenario_name, gchar ** original_name) +{ + GList *res = NULL, *tmp; + GstStructure *meta = get_test_file_meta (); + + if (!testfile_structs || testfile_used) + return FALSE; + + if (meta && gst_structure_has_field (meta, "scenario")) { + *scenario_name = gst_structure_get_string (meta, "scenario"); + + return TRUE; + } + + for (tmp = testfile_structs; tmp; tmp = tmp->next) { + GstStructure *structure = NULL; + + if (gst_structure_has_name (tmp->data, "set-globals")) + continue; + + structure = gst_structure_copy (tmp->data); + if (gst_structure_has_name (structure, "meta")) + gst_structure_remove_fields (structure, "configs", "gst-validate-args", + NULL); + res = g_list_append (res, structure); + } + + *structs = res; + *original_name = global_testfile; + testfile_used = TRUE; + + return TRUE; +} + +#if !GLIB_CHECK_VERSION(2,58,0) + +/* Copied from https://gitlab.gnome.org/GNOME/glib/-/blob/main/glib/gfileutils.c#L2736 */ +static gchar * +g_canonicalize_filename (const gchar * filename, const gchar * relative_to) +{ + gchar *canon, *start, *p, *q; + guint i; + + g_return_val_if_fail (relative_to == NULL + || g_path_is_absolute (relative_to), NULL); + + if (!g_path_is_absolute (filename)) { + gchar *cwd_allocated = NULL; + const gchar *cwd; + + if (relative_to != NULL) + cwd = relative_to; + else + cwd = cwd_allocated = g_get_current_dir (); + + canon = g_build_filename (cwd, filename, NULL); + g_free (cwd_allocated); + } else { + canon = g_strdup (filename); + } + + start = (char *) g_path_skip_root (canon); + + if (start == NULL) { + /* This shouldn't really happen, as g_get_current_dir() should + return an absolute pathname, but bug 573843 shows this is + not always happening */ + g_free (canon); + return g_build_filename (G_DIR_SEPARATOR_S, filename, NULL); + } + + /* POSIX allows double slashes at the start to + * mean something special (as does windows too). + * So, "//" != "/", but more than two slashes + * is treated as "/". + */ + i = 0; + for (p = start - 1; (p >= canon) && G_IS_DIR_SEPARATOR (*p); p--) + i++; + if (i > 2) { + i -= 1; + start -= i; + memmove (start, start + i, strlen (start + i) + 1); + } + + /* Make sure we're using the canonical dir separator */ + p++; + while (p < start && G_IS_DIR_SEPARATOR (*p)) + *p++ = G_DIR_SEPARATOR; + + p = start; + while (*p != 0) { + if (p[0] == '.' && (p[1] == 0 || G_IS_DIR_SEPARATOR (p[1]))) { + memmove (p, p + 1, strlen (p + 1) + 1); + } else if (p[0] == '.' && p[1] == '.' && (p[2] == 0 + || G_IS_DIR_SEPARATOR (p[2]))) { + q = p + 2; + /* Skip previous separator */ + p = p - 2; + if (p < start) + p = start; + while (p > start && !G_IS_DIR_SEPARATOR (*p)) + p--; + if (G_IS_DIR_SEPARATOR (*p)) + *p++ = G_DIR_SEPARATOR; + memmove (p, q, strlen (q) + 1); + } else { + /* Skip until next separator */ + while (*p != 0 && !G_IS_DIR_SEPARATOR (*p)) + p++; + + if (*p != 0) { + /* Canonicalize one separator */ + *p++ = G_DIR_SEPARATOR; + } + } + + /* Remove additional separators */ + q = p; + while (*q && G_IS_DIR_SEPARATOR (*q)) + q++; + + if (p != q) + memmove (p, q, strlen (q) + 1); + } + + /* Remove trailing slashes */ + if (p > start && G_IS_DIR_SEPARATOR (*(p - 1))) + *(p - 1) = 0; + + return canon; +} + +#endif + +/* Only the first monitor pipeline will be used */ +GstStructure * +gst_validate_setup_test_file (const gchar * testfile, gboolean use_fakesinks) +{ + const gchar *tool; + GstStructure *res = NULL; + + g_assert (!got_configs); + if (global_testfile) + gst_validate_abort ("A testfile was already loaded: %s", global_testfile); + + global_testfile = g_canonicalize_filename (testfile, NULL); + + gst_validate_set_globals (NULL); + gst_validate_structure_set_variables_from_struct_file (NULL, global_testfile); + testfile_structs = + gst_validate_utils_structs_parse_from_filename (global_testfile, NULL, + NULL); + + if (!testfile_structs) + gst_validate_abort ("Could not load test file: %s", global_testfile); + + res = testfile_structs->data; + if (gst_structure_has_name (testfile_structs->data, "set-globals")) { + GstStructure *globals = testfile_structs->data; + gst_validate_set_globals (globals); + if (!testfile_structs->next) + gst_validate_abort + ("Only one `set-globals` structure in %s, nothing to test here.", + global_testfile); + res = testfile_structs->next->data; + } + + if (!gst_structure_has_name (res, "meta")) + gst_validate_abort + ("First structure of a .validatetest file should be a `meta` or " + "`set-gobals` then `meta`, got: %s", gst_structure_to_string (res)); + + register_action_types (); + gst_validate_scenario_check_and_set_needs_clock_sync (testfile_structs, &res); + + gst_validate_set_test_file_globals (res, global_testfile, use_fakesinks); + gst_validate_structure_resolve_variables (NULL, res, NULL, 0); + + tool = gst_structure_get_string (res, "tool"); + if (!tool) + tool = "gst-validate-" GST_API_VERSION; + + if (g_strcmp0 (tool, g_get_prgname ())) + gst_validate_abort + ("Validate test file: '%s' was made to be run with '%s' not '%s'", + global_testfile, tool, g_get_prgname ()); + + return res; +} diff --git a/validate/gst/validate/validate.h b/validate/gst/validate/validate.h new file mode 100644 index 0000000000..64691b575a --- /dev/null +++ b/validate/gst/validate/validate.h @@ -0,0 +1,38 @@ +/* GStreamer + * Copyright (C) 2013 Thiago Santos + */ + +#ifndef _GST_VALIDATE_H +#define _GST_VALIDATE_H + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +G_BEGIN_DECLS + +GST_VALIDATE_API +void gst_validate_init (void); +GST_VALIDATE_API +void gst_validate_init_debug (void); +GST_VALIDATE_API +void gst_validate_deinit (void); +GST_VALIDATE_API +GList * gst_validate_plugin_get_config (GstPlugin * plugin); +GST_VALIDATE_API +gboolean gst_validate_is_initialized (void); +GST_VALIDATE_API +GstStructure *gst_validate_setup_test_file(const gchar * testfile, gboolean use_fakesinks); + +G_END_DECLS + +#endif /* _GST_VALIDATE_H */ diff --git a/validate/launcher/RangeHTTPServer.py b/validate/launcher/RangeHTTPServer.py new file mode 100644 index 0000000000..aa0f048a2c --- /dev/null +++ b/validate/launcher/RangeHTTPServer.py @@ -0,0 +1,291 @@ +#!/usr/bin/env python3 + +# Portions Copyright (C) 2009,2010 Xyne +# Portions Copyright (C) 2011 Sean Goller +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# (version 2) as published by the Free Software Foundation. +# +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + + +"""Range HTTP Server. + +This module builds on BaseHTTPServer by implementing the standard GET +and HEAD requests in a fairly straightforward manner, and includes support +for the Range header. + +""" + + +__version__ = "0.1" + +__all__ = ["RangeHTTPRequestHandler"] + +import os +import sys + +from socketserver import ThreadingMixIn + +import posixpath +import http.server +import urllib.parse +import html +import shutil +import mimetypes +import io +import time + + +_bandwidth = 0 + + +class ThreadingSimpleServer(ThreadingMixIn, http.server.HTTPServer): + pass + + +class RangeHTTPRequestHandler(http.server.BaseHTTPRequestHandler): + + """Simple HTTP request handler with GET and HEAD commands. + + This serves files from the current directory and any of its + subdirectories. The MIME type for files is determined by + calling the .guess_type() method. + + The GET and HEAD requests are identical except that the HEAD + request omits the actual contents of the file. + + """ + + server_version = "RangeHTTP/" + __version__ + + def do_GET(self): + """Serve a GET request.""" + f, start_range, end_range = self.send_head() + print("Got values of {} and {}".format(start_range, end_range)) + if f: + f.seek(start_range, 0) + chunk = 0x1000 + total = 0 + while chunk > 0: + if start_range + chunk > end_range: + chunk = end_range - start_range + + if _bandwidth != 0: + time_to_sleep = float(float(chunk) / float(_bandwidth)) + time.sleep(time_to_sleep) + + try: + self.wfile.write(f.read(chunk)) + except Exception: + break + total += chunk + start_range += chunk + f.close() + + def do_HEAD(self): + """Serve a HEAD request.""" + f, start_range, end_range = self.send_head() + if f: + f.close() + + def send_head(self): + """Common code for GET and HEAD commands. + + This sends the response code and MIME headers. + + Return value is either a file object (which has to be copied + to the outputfile by the caller unless the command was HEAD, + and must be closed by the caller under all circumstances), or + None, in which case the caller has nothing further to do. + + """ + path = self.translate_path(self.path) + f = None + if os.path.isdir(path): + if not self.path.endswith("/"): + # redirect browser + self.send_response(301) + self.send_header("Location", self.path + "/") + self.end_headers() + return (None, 0, 0) + for index in "index.html", "index.html": + index = os.path.join(path, index) + if os.path.exists(index): + path = index + break + else: + return self.list_directory(path) + ctype = self.guess_type(path) + + try: + # Always read in binary mode. Opening files in text mode may cause + # newline translations, making the actual size of the content + # transmitted *less* than the content-length! + f = open(path, "rb") + except IOError: + self.send_error(404, "File not found") + return (None, 0, 0) + + if "Range" in self.headers: + self.send_response(206) # partial content response + else: + self.send_response(200) + + self.send_header("Content-type", ctype) + file_size = os.path.getsize(path) + + start_range = 0 + end_range = file_size + + self.send_header("Accept-Ranges", "bytes") + if "Range" in self.headers: + s, e = self.headers['range'][6:].split('-', 1) # bytes:%d-%d + sl = len(s) + el = len(e) + + if sl: + start_range = int(s) + if el: + end_range = int(e) + 1 + elif el: + start_range = file_size - min(file_size, int(e)) + + self.send_header("Content-Range", "bytes {}-{}/{}".format(start_range, end_range, file_size)) + self.send_header("Content-Length", end_range - start_range) + self.end_headers() + + print("Sending bytes {} to {}...".format(start_range, end_range)) + return (f, start_range, end_range) + + def list_directory(self, path): + """Helper to produce a directory listing (absent index.html). + + Return value is either a file object, or None (indicating an + error). In either case, the headers are sent, making the + interface the same as for send_head(). + + """ + try: + lst = os.listdir(path) + except OSError: + self.send_error(404, "Access Forbidden") + return None + + lst.sort(key=lambda file_name: file_name.lower()) + html_text = [] + + displaypath = html.escape(urllib.parse.unquote(self.path)) + html_text.append('') + html_text.append("\nDirectory listing for {}\n".format(displaypath)) + html_text.append("\n

Directory listing for {}

\n".format(displaypath)) + html_text.append("
\n
    \n") + + for name in lst: + fullname = os.path.join(path, name) + displayname = linkname = name + + if os.path.isdir(fullname): + displayname = name + "/" + linkname = name + "/" + + if os.path.islink(fullname): + displayname = name + "@" + + html_text.append('
  • {}\n'.format(urllib.parse.quote(linkname), html.escape(displayname))) + + html_text.append('
\n\n\n\n') + + byte_encoded_string = "\n".join(html_text).encode("utf-8", "surrogateescape") + f = io.BytesIO() + f.write(byte_encoded_string) + length = len(byte_encoded_string) + + f.seek(0) + + self.send_response(200) + self.send_header("Content-type", "text/html") + self.send_header("Content-length", str(length)) + self.end_headers() + + return (f, 0, length) + + def translate_path(self, path): + """Translate a /-separated PATH to the local filename syntax. + + Components that mean special things to the local file system + (e.g. drive or directory names) are ignored. (XXX They should + probably be diagnosed.) + + """ + # abandon query parameters + path = path.split("?", 1)[0] + path = path.split("#", 1)[0] + path = posixpath.normpath(urllib.parse.unquote(path)) + words = path.split("/") + words = filter(None, words) + path = os.getcwd() + + for word in words: + drive, word = os.path.splitdrive(word) + head, word = os.path.split(word) + if word in (os.curdir, os.pardir): + continue + path = os.path.join(path, word) + return path + + def guess_type(self, path): + """Guess the type of a file. + + Argument is a PATH (a filename). + + Return value is a string of the form type/subtype, + usable for a MIME Content-type header. + + The default implementation looks the file's extension + up in the table self.extensions_map, using application/octet-stream + as a default; however it would be permissible (if + slow) to look inside the data to make a better guess. + + """ + + base, ext = posixpath.splitext(path) + if ext in self.extension_map: + return self.extension_map[ext] + ext = ext.lower() + if ext in self.extension_map: + return self.extension_map[ext] + else: + return self.extension_map[''] + + if not mimetypes.inited: + mimetypes.init() + extension_map = mimetypes.types_map.copy() + extension_map.update({ + '': 'application/octet-stream', # Default + '.py': 'text/plain', + '.c': 'text/plain', + '.h': 'text/plain', + '.mp4': 'video/mp4', + '.ogg': 'video/ogg', + '.java': 'text/plain', + }) + + +def test(handler_class=RangeHTTPRequestHandler, server_class=http.server.HTTPServer): + http.server.test(handler_class, server_class) + + +if __name__ == "__main__": + httpd = ThreadingSimpleServer(("0.0.0.0", int(sys.argv[1])), RangeHTTPRequestHandler) + httpd.serve_forever() diff --git a/validate/launcher/__init__.py b/validate/launcher/__init__.py new file mode 100644 index 0000000000..f772b2eb68 --- /dev/null +++ b/validate/launcher/__init__.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2014,Thibault Saunier +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +from pkgutil import extend_path +__path__ = extend_path(__path__, __name__) diff --git a/validate/launcher/apps/__init__.py b/validate/launcher/apps/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/validate/launcher/apps/gstcheck.py b/validate/launcher/apps/gstcheck.py new file mode 100644 index 0000000000..8b29fb8cf3 --- /dev/null +++ b/validate/launcher/apps/gstcheck.py @@ -0,0 +1,409 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2016,Thibault Saunier +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. +import argparse +import json +import os +import sys +import re +import pickle +import platform +import shutil +import subprocess +import threading +import concurrent.futures as conc + + +from launcher import config +from launcher.utils import printc, Colors, get_gst_build_valgrind_suppressions +from launcher.main import setup_launcher_from_args +from launcher.baseclasses import VALGRIND_TIMEOUT_FACTOR +from launcher.apps.gstvalidate import GstValidateSimpleTest + + +class MesonTest(Test): + + def __init__(self, name, options, reporter, test_infos, child_env=None): + ref_env = os.environ.copy() + if child_env is None: + child_env = {} + else: + ref_env.update(child_env) + + child_env.update(test_infos['env']) + self.child_env = child_env + + timeout = int(test_infos['timeout']) + Test.__init__(self, test_infos['cmd'][0], name, options, + reporter, timeout=timeout, hard_timeout=timeout, + is_parallel=test_infos.get('is_parallel', True), + workdir=test_infos['workdir']) + + self.test_infos = test_infos + + def build_arguments(self): + self.add_arguments(*self.test_infos['cmd'][1:]) + + def get_subproc_env(self): + env = os.environ.copy() + env.update(self.child_env) + for var, val in self.child_env.items(): + if val != os.environ.get(var): + self.add_env_variable(var, val) + env["GST_VALIDATE_LOGSDIR"] = self.options.logsdir + + return env + + +class GstCheckTest(MesonTest): + __gst_paths = {} + + def get_valgrind_suppressions(self): + result = super().get_valgrind_suppressions() + result.extend(get_gst_build_valgrind_suppressions()) + + return result + + def get_subproc_env(self): + bdir = self.test_infos['__bdir__'] + + env = super().get_subproc_env() + if 'GST_PLUGIN_PATH' not in env and 'GST_PLUGIN_PATH_1_0' not in env: + return env + + plugins_path = self.__gst_paths.get(bdir) + if not plugins_path: + try: + with open(os.path.join(bdir, "GstPluginsPath.json")) as f: + plugins_path = self.__gst_paths[bdir] = set(json.load(f)) + except FileNotFoundError: + pass + + if not plugins_path: + return env + + cpath = set(env.get('GST_PLUGIN_PATH', '').split(os.pathsep)) | set(env.get('GST_PLUGIN_PATH_1_0', '').split(os.pathsep)) + cpath -= set(self.options.meson_build_dirs) + cpath |= plugins_path + + env['GST_REGISTRY'] = os.path.normpath(bdir + "/registry.dat") + env['GST_PLUGIN_PATH'] = os.pathsep.join(cpath) + if 'GST_PLUGIN_PATH_1_0' in env: + del env['GST_PLUGIN_PATH_1_0'] + + return env + + +class MesonTestsManager(TestsManager): + name = "mesontest" + arggroup = None + + def __init__(self): + super().__init__() + self.rebuilt = None + self._registered = False + + def add_options(self, parser): + if self.arggroup: + return + + arggroup = MesonTestsManager.arggroup = parser.add_argument_group( + "meson tests specific options and behaviours") + arggroup.add_argument("--meson-build-dir", + action="append", + dest='meson_build_dirs', + default=[], + help="defines the paths to look for GstValidate tools.") + arggroup.add_argument("--meson-no-rebuild", + action="store_true", + default=False, + help="Whether to avoid to rebuild tests before running them.") + + def get_meson_tests(self): + meson = shutil.which('meson') + if not meson: + meson = shutil.which('meson.py') + if not meson: + printc("Can't find meson, can't run testsuite.\n", Colors.FAIL) + return False + + if not self.options.meson_build_dirs: + self.options.meson_build_dirs = [config.BUILDDIR] + + self.options.meson_build_dirs = [os.path.realpath(p) for p in self.options.meson_build_dirs] + + mesontests = [] + for i, bdir in enumerate(self.options.meson_build_dirs): + bdir = os.path.abspath(bdir) + output = subprocess.check_output( + [meson, 'introspect', '--tests', bdir]) + + for test_dict in json.loads(output.decode()): + test_dict['__bdir__'] = bdir + mesontests.append(test_dict) + + return mesontests + + def rebuild(self, all=False): + if not self.options.meson_build_dirs: + self.options.meson_build_dirs = [config.BUILDDIR] + if self.options.meson_no_rebuild: + return True + + if self.rebuilt is not None: + return self.rebuilt + + for bdir in self.options.meson_build_dirs: + if not os.path.isfile(os.path.join(bdir, 'build.ninja')): + printc("Only ninja backend is supported to rebuilt tests before running them.\n", + Colors.OKBLUE) + self.rebuilt = True + return True + + ninja = shutil.which('ninja') + if not ninja: + ninja = shutil.which('ninja-build') + if not ninja: + printc("Can't find ninja, can't rebuild test.\n", Colors.FAIL) + self.rebuilt = False + return False + + print("-> Rebuilding %s.\n" % bdir) + try: + subprocess.check_call([ninja, '-C', bdir]) + except subprocess.CalledProcessError: + self.rebuilt = False + return False + + self.rebuilt = True + return True + + def run_tests(self, starting_test_num, total_num_tests): + if not self.rebuild(): + self.error("Rebuilding FAILED!") + return Result.FAILED + + return TestsManager.run_tests(self, starting_test_num, total_num_tests) + + def get_test_name(self, test): + name = test['name'].replace('/', '.') + if test['suite']: + name = '.'.join(test['suite']) + '.' + name + + return name.replace('..', '.').replace(' ', '-') + + def list_tests(self): + if self._registered is True: + return self.tests + + mesontests = self.get_meson_tests() + for test in mesontests: + if not self.setup_tests_from_sublauncher(test): + self.add_test(MesonTest(self.get_test_name(test), + self.options, self.reporter, test)) + + self._registered = True + return self.tests + + def setup_tests_from_sublauncher(self, test): + cmd = test['cmd'] + binary = cmd[0] + sublauncher_tests = set() + if binary != sys.argv[0]: + return sublauncher_tests + + res, _, tests_launcher = setup_launcher_from_args(cmd[1:], main_options=self.options) + if res is False: + return sublauncher_tests + + for sublauncher_test in tests_launcher.list_tests(): + name = self.get_test_name(test) + sublauncher_tests.add(name) + + sublauncher_test.generator = None + sublauncher_test.options = self.options + sublauncher_test.classname = name + '.' + sublauncher_test.classname + self.add_test(sublauncher_test) + + return sublauncher_tests + + +class GstCheckTestsManager(MesonTestsManager): + name = "check" + + def __init__(self): + MesonTestsManager.__init__(self) + self.tests_info = {} + + def init(self): + return True + + def check_binary_ts(self, binary): + try: + last_touched = os.stat(binary).st_mtime + test_info = self.tests_info.get(binary) + if not test_info: + return last_touched, [] + elif test_info[0] == 0: + return True + elif test_info[0] == last_touched: + return True + except FileNotFoundError: + return None + + return last_touched, [] + + def _list_gst_check_tests(self, test, recurse=False): + binary = test['cmd'][0] + + self.tests_info[binary] = self.check_binary_ts(binary) + + tmpenv = os.environ.copy() + tmpenv['GST_DEBUG'] = "0" + pe = subprocess.Popen([binary, '--list-tests'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + env=tmpenv) + + output = pe.communicate()[0].decode() + if pe.returncode != 0: + self.debug("%s not able to list tests" % binary) + return + for t in output.split("\n"): + test_name = re.findall(r'(?<=^Test: )\w+$', t) + if len(test_name) == 1: + self.tests_info[binary][1].append(test_name[0]) + + def load_tests_info(self): + dumpfile = os.path.join(self.options.privatedir, self.name + '.dat') + try: + with open(dumpfile, 'rb') as f: + self.tests_info = pickle.load(f) + except FileNotFoundError: + self.tests_info = {} + + def save_tests_info(self): + dumpfile = os.path.join(self.options.privatedir, self.name + '.dat') + with open(dumpfile, 'wb') as f: + pickle.dump(self.tests_info, f) + + def add_options(self, parser): + super().add_options(parser) + arggroup = parser.add_argument_group("gstcheck specific options") + arggroup.add_argument("--gst-check-leak-trace-testnames", + default=None, + help="A regex to specifying testsnames of the test" + "to run with the leak tracer activated, if 'known-not-leaky'" + " is specified, the testsuite will automatically activate" + " leak tracers on tests known to be not leaky.") + arggroup.add_argument("--gst-check-leak-options", + default=None, + help="Leak tracer options") + + def get_child_env(self, testname, check_name=None): + child_env = {} + if check_name: + child_env['GST_CHECKS'] = check_name + + if self.options.valgrind: + child_env['CK_TIMEOUT_MULTIPLIER'] = str(VALGRIND_TIMEOUT_FACTOR * self.options.timeout_factor) + child_env['ORC_CODE'] = 'backup' + else: + child_env['CK_TIMEOUT_MULTIPLIER'] = str(self.options.timeout_factor) + + if self.options.gdb or self.options.rr: + child_env['CK_FORK'] = "no" + + if self.options.gst_check_leak_trace_testnames: + if re.findall(self.options.gst_check_leak_trace_testnames, testname): + leak_tracer = "leaks" + if self.options.gst_check_leak_options: + leak_tracer += "(%s)" % self.options.gst_check_leak_options + tracers = set(os.environ.get('GST_TRACERS', '').split( + ';')) | set([leak_tracer]) + child_env['GST_TRACERS'] = ';'.join(tracers) + + return child_env + + def register_tests(self): + if self.tests: + return self.tests + + if not self.rebuild(): + raise RuntimeError("Could not rebuild GStreamer unit tests") + + self.load_tests_info() + mesontests = self.get_meson_tests() + to_inspect = [] + all_sublaunchers_tests = set() + for test in mesontests: + sublauncher_tests = self.setup_tests_from_sublauncher(test) + if sublauncher_tests: + all_sublaunchers_tests |= sublauncher_tests + continue + binary = test['cmd'][0] + test_info = self.check_binary_ts(binary) + if test_info is True: + continue + elif test_info is None: + test_info = self.check_binary_ts(binary) + if test_info is None: + raise RuntimeError("Test binary %s does not exist" + " even after a full rebuild" % binary) + + with open(binary, 'rb') as f: + if b"gstcheck" not in f.read(): + self.tests_info[binary] = [0, []] + continue + to_inspect.append(test) + + if to_inspect: + assert self.options.num_jobs >= 0 + executor = conc.ThreadPoolExecutor( + max_workers=self.options.num_jobs) + tmp = [] + for test in to_inspect: + tmp.append(executor.submit(self._list_gst_check_tests, test)) + + for e in tmp: + e.result() + + for test in mesontests: + name = self.get_test_name(test) + if name in all_sublaunchers_tests: + continue + gst_tests = self.tests_info[test['cmd'][0]][1] + if os.path.basename(test['cmd'][0]) in \ + ['gst-tester-1.0', 'gst-tester-1.0.exe']: + fpath = test['cmd'][1] + self.add_test(GstValidateSimpleTest(fpath, name, + self.options, + self.reporter)) + continue + if not gst_tests: + child_env = self.get_child_env(name) + self.add_test(GstCheckTest(name, self.options, self.reporter, test, + child_env)) + else: + for ltest in gst_tests: + name = self.get_test_name(test) + '.' + ltest + child_env = self.get_child_env(name, ltest) + self.add_test(GstCheckTest(name, self.options, self.reporter, test, + child_env)) + self.save_tests_info() + self._registered = True + return self.tests diff --git a/validate/launcher/apps/gstvalidate.py b/validate/launcher/apps/gstvalidate.py new file mode 100644 index 0000000000..b9b11dd5ea --- /dev/null +++ b/validate/launcher/apps/gstvalidate.py @@ -0,0 +1,1367 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2013,Thibault Saunier +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. +import argparse +import os +import copy +import sys +import time +import urllib.parse +import shlex +import socket +import subprocess +import configparser +import json +import glob +import math +from launcher.loggable import Loggable, error + +from launcher.baseclasses import GstValidateTest, Test, \ + ScenarioManager, NamedDic, GstValidateTestsGenerator, \ + GstValidateMediaDescriptor, GstValidateEncodingTestInterface, \ + GstValidateBaseTestManager, MediaDescriptor, MediaFormatCombination + +from launcher.utils import path2url, url2path, DEFAULT_TIMEOUT, which, \ + GST_SECOND, Result, Protocols, mkdir, printc, Colors, get_data_file, \ + kill_subprocess, format_config_template, get_fakesink_for_media_type, \ + parse_gsttimeargs, GstCaps + +# +# Private global variables # +# + +# definitions of commands to use +parser = argparse.ArgumentParser(add_help=False) +parser.add_argument("--validate-tools-path", dest="validate_tools_path", + default="", + help="defines the paths to look for GstValidate tools.") +options, args = parser.parse_known_args() + +GstValidateBaseTestManager.update_commands(options.validate_tools_path) +AUDIO_ONLY_FILE_TRANSCODING_RATIO = 5 + +# +# API to be used to create testsuites # +# + +""" +Some info about protocols and how to handle them +""" +GST_VALIDATE_CAPS_TO_PROTOCOL = [("application/x-hls", Protocols.HLS), + ("application/dash+xml", Protocols.DASH)] + + +def expand_vars_in_list_recurse(l, data): + for i, v in enumerate(l): + if isinstance(v, dict): + l[i] = expand_vars_in_dict_recurse(v, data) + elif isinstance(v, str): + l[i] = v % data + elif isinstance(v, list): + l[i] = expand_vars_in_list_recurse(v, data) + + return l + + +def expand_vars_in_dict_recurse(dico, data): + for key, value in dico.items(): + if isinstance(value, dict): + dico[key] = expand_vars_in_dict_recurse(value, data) + elif isinstance(value, str): + dico[key] = value % data + elif isinstance(value, list): + dico[key] = expand_vars_in_list_recurse(value, data) + + return dico + + +class GstValidateMediaCheckTestsGenerator(GstValidateTestsGenerator): + + def __init__(self, test_manager): + GstValidateTestsGenerator.__init__(self, "media_check", test_manager) + + def populate_tests(self, uri_minfo_special_scenarios, scenarios): + for uri, mediainfo, special_scenarios in uri_minfo_special_scenarios: + protocol = mediainfo.media_descriptor.get_protocol() + timeout = DEFAULT_TIMEOUT + + classname = "%s.media_check.%s" % (protocol, + os.path.basename(url2path(uri)).replace(".", "_")) + self.add_test(GstValidateMediaCheckTest(classname, + self.test_manager.options, + self.test_manager.reporter, + mediainfo.media_descriptor, + uri, + mediainfo.path, + timeout=timeout)) + + +class GstValidateTranscodingTestsGenerator(GstValidateTestsGenerator): + HARD_TIMEOUT_FACTOR = 10 + + def __init__(self, test_manager): + GstValidateTestsGenerator.__init__(self, "transcode", test_manager) + + def populate_tests(self, uri_minfo_special_scenarios, scenarios): + for uri, mediainfo, special_scenarios in uri_minfo_special_scenarios: + if mediainfo.media_descriptor.is_image(): + continue + + protocol = mediainfo.media_descriptor.get_protocol() + if protocol == Protocols.RTSP: + continue + + options = self.test_manager.options + for comb in self.test_manager.get_encoding_formats(): + classname = "%s.transcode.to_%s.%s" % (mediainfo.media_descriptor.get_protocol(), + str(comb).replace( + ' ', '_'), + mediainfo.media_descriptor.get_clean_name()) + + self.add_test(GstValidateTranscodingTest(classname, + options, + self.test_manager.reporter, + comb, + uri, + mediainfo.media_descriptor)) + + +class FakeMediaDescriptor(MediaDescriptor): + + def __init__(self, infos, pipeline_desc): + MediaDescriptor.__init__(self) + self._infos = infos + self._pipeline_desc = pipeline_desc + + def get_path(self): + return self._infos.get('path', None) + + def get_tracks_caps(self): + return self._info.get('tracks-caps', []) + + def get_media_filepath(self): + return self._infos.get('media-filepath', None) + + def get_caps(self): + return self._infos.get('caps', None) + + def get_uri(self): + return self._infos.get('uri', None) + + def get_duration(self): + return int(self._infos.get('duration', 0)) * GST_SECOND + + def get_protocol(self): + return self._infos.get('protocol', "launch_pipeline") + + def is_seekable(self): + return self._infos.get('is-seekable', True) + + def is_image(self): + return self._infos.get('is-image', False) + + def is_live(self): + return self._infos.get('is-live', False) + + def get_num_tracks(self, track_type): + return self._infos.get('num-%s-tracks' % track_type, + self._pipeline_desc.count(track_type + "sink")) + + def can_play_reverse(self): + return self._infos.get('plays-reverse', False) + + +class GstValidateSimpleTestsGenerator(GstValidateTestsGenerator): + def __init__(self, name, test_manager, tests_dir): + self.tests_dir = tests_dir + super().__init__(name, test_manager) + + def populate_tests(self, uri_minfo_special_scenarios, scenarios): + for root, _, files in os.walk(self.tests_dir): + for f in files: + name, ext = os.path.splitext(f) + if ext != ".validatetest": + continue + + fpath = os.path.abspath(os.path.join(root, f)) + pathname = os.path.abspath(os.path.join(root, name)) + name = pathname.replace(os.path.commonpath([self.tests_dir, root]), '').replace('/', '.') + self.add_test(GstValidateSimpleTest(fpath, 'test' + name, + self.test_manager.options, + self.test_manager.reporter)) + + +class GstValidatePipelineTestsGenerator(GstValidateTestsGenerator): + + def __init__(self, name, test_manager, pipeline_template=None, + pipelines_descriptions=None, valid_scenarios=None): + """ + @name: The name of the generator + @pipeline_template: A template pipeline to be used to generate actual pipelines + @pipelines_descriptions: A list of tuple of the form: + (test_name, pipeline_description, extra_data) + extra_data being a dictionary with the following keys: + 'scenarios': ["the", "valide", "scenarios", "names"] + 'duration': the_duration # in seconds + 'timeout': a_timeout # in seconds + 'hard_timeout': a_hard_timeout # in seconds + + @valid_scenarios: A list of scenario name that can be used with that generator + """ + valid_scenarios = valid_scenarios or [] + GstValidateTestsGenerator.__init__(self, name, test_manager) + self._pipeline_template = pipeline_template + self._pipelines_descriptions = [] + for description in pipelines_descriptions or []: + if not isinstance(description, dict): + desc_dict = {"name": description[0], + "pipeline": description[1]} + if len(description) >= 3: + desc_dict["extra_data"] = description[2] + self._pipelines_descriptions.append(desc_dict) + else: + self._pipelines_descriptions.append(description) + self._valid_scenarios = valid_scenarios + + @staticmethod + def get_config_file(config, private_dir, test_name, extra_data): + if isinstance(config, str): + return config + + os.makedirs(private_dir, exist_ok=True) + config_file = os.path.join(private_dir, test_name + '.config') + with open(config_file, 'w') as f: + f.write(format_config_template(extra_data, + '\n'.join(config) + '\n', test_name)) + + return config_file + + @classmethod + def from_dict(cls, test_manager, name, descriptions, extra_data=None): + """ + :param json_file: Path to a JSON file containing pipeline tests. + :param extra_data: Variables available for interpolation in validate + configs and scenario actions. + """ + if extra_data is None: + extra_data = {} + + pipelines_descriptions = [] + for test_name, defs in descriptions.items(): + tests_definition = {'name': test_name, 'pipeline': defs.pop('pipeline')} + test_private_dir = os.path.join(test_manager.options.privatedir, + name, test_name) + + config_files = {} + config = defs.pop('config', None) + timeout = defs.pop('timeout', DEFAULT_TIMEOUT) + scenario_defs = defs.pop('scenarios', []) + if not scenario_defs and config: + config_files[None] = cls.get_config_file(config, test_private_dir, test_name, extra_data) + + scenarios = [] + for scenario in scenario_defs: + if isinstance(scenario, str): + # Path to a scenario file + scenarios.append(scenario) + scenario_name = os.path.basename(scenario).replace('.scenario', '') + test_private_dir = os.path.join(test_manager.options.privatedir, + name, test_name, scenario_name) + else: + # Dictionary defining a new scenario in-line + scenario_name = scenario_file = scenario['name'] + test_private_dir = os.path.join(test_manager.options.privatedir, + name, test_name, scenario_name) + actions = scenario.get('actions') + if actions: + os.makedirs(test_private_dir, exist_ok=True) + scenario_file = os.path.join( + test_private_dir, scenario_name + '.scenario') + with open(scenario_file, 'w') as f: + f.write('\n'.join(action % extra_data for action in actions) + '\n') + scenarios.append(scenario_file) + + if config: + config_files[scenario_name] = cls.get_config_file(config, test_private_dir, test_name + '.' + scenario_name, extra_data) + + local_extra_data = extra_data.copy() + local_extra_data.update(defs) + envvars = defs.pop('extra_env_vars', {}) + local_extra_data.update({ + 'scenarios': scenarios, + 'config_files': config_files, + 'plays-reverse': True, + 'extra_env_vars': envvars, + 'timeout': timeout, + }) + + expand_vars_in_dict_recurse(local_extra_data, extra_data) + tests_definition['extra_data'] = local_extra_data + tests_definition['pipeline_data'] = {} + tests_definition['pipeline_data'].update(local_extra_data) + pipelines_descriptions.append(tests_definition) + + return GstValidatePipelineTestsGenerator(name, test_manager, pipelines_descriptions=pipelines_descriptions) + + def get_fname(self, scenario, protocol=None, name=None): + if name is None: + name = self.name + + if protocol is not None: + protocol_str = "%s." % protocol + else: + protocol_str = "" + + if scenario is not None and scenario.name.lower() != "none": + return "%s%s.%s" % (protocol_str, name, scenario.name) + + return ("%s.%s.%s" % (protocol_str, self.name, name)).replace("..", ".") + + def generate_tests(self, uri_minfo_special_scenarios, scenarios): + if self._valid_scenarios is None: + scenarios = [None] + elif self._valid_scenarios: + scenarios = [scenario for scenario in scenarios if + scenario is not None and scenario.name in self._valid_scenarios] + + return super(GstValidatePipelineTestsGenerator, self).generate_tests( + uri_minfo_special_scenarios, scenarios) + + def populate_tests(self, uri_minfo_special_scenarios, scenarios): + + special_scenarios = [] + for description in self._pipelines_descriptions: + for s in description.get('extra_data', {}).get('scenarios', []): + if os.path.isabs(s): + special_scenarios.append(s) + + self.test_manager.scenarios_manager.discover_scenarios(special_scenarios) + for description in self._pipelines_descriptions: + pipeline = description['pipeline'] + extra_data = description.get('extra_data', {}) + pipeline_data = description.get('pipeline_data', {}) + + if 'scenarios' in extra_data: + # A pipeline description can override the default scenario set. + # The pipeline description may specify an empty list of + # scenarios, in which case one test will be generated with no + # scenario. + scenarios_to_iterate = extra_data['scenarios'] or [None] + else: + scenarios_to_iterate = scenarios + + config_files = extra_data.get('config_files') + timeout = extra_data.get('timeout', DEFAULT_TIMEOUT) + mediainfo = extra_data.get( + 'media_info', FakeMediaDescriptor(extra_data, pipeline)) + for scenario in scenarios_to_iterate: + if isinstance(scenario, str): + tmpscenario = self.test_manager.scenarios_manager.get_scenario( + scenario) + if tmpscenario is None: + raise RuntimeError("Could not find scenario file: %s" % scenario) + scenario = tmpscenario + + if not mediainfo.is_compatible(scenario): + continue + + if self.test_manager.options.mute: + needs_clock = scenario.needs_clock_sync() \ + if scenario else False + audiosink = get_fakesink_for_media_type("audio", needs_clock) + videosink = get_fakesink_for_media_type("video", needs_clock) + else: + audiosink = 'autoaudiosink' + videosink = 'autovideosink' + + pipeline_data.update({'videosink': videosink, 'audiosink': audiosink}) + pipeline_desc = pipeline % pipeline_data + + fname = self.get_fname( + scenario, protocol=mediainfo.get_protocol(), name=description["name"]) + + expected_issues = extra_data.get("expected-issues") + extra_env_vars = extra_data.get("extra_env_vars") + test = GstValidateLaunchTest(fname, + self.test_manager.options, + self.test_manager.reporter, + pipeline_desc, + scenario=scenario, + timeout=timeout, + media_descriptor=mediainfo, + expected_issues=expected_issues, + extra_env_variables=extra_env_vars) + if config_files: + test.add_validate_config(config_files[scenario.name if scenario is not None else None]) + self.add_test(test) + + +class GstValidatePlaybinTestsGenerator(GstValidatePipelineTestsGenerator): + + def __init__(self, test_manager): + if os.getenv("USE_PLAYBIN3") is None: + GstValidatePipelineTestsGenerator.__init__( + self, "playback", test_manager, "playbin") + else: + GstValidatePipelineTestsGenerator.__init__( + self, "playback", test_manager, "playbin3") + + def _set_sinks(self, minfo, pipe_str, scenario): + if self.test_manager.options.mute: + needs_clock = scenario.needs_clock_sync() or minfo.media_descriptor.need_clock_sync() + + afakesink = get_fakesink_for_media_type("audio", needs_clock) + vfakesink = get_fakesink_for_media_type("video", needs_clock) + pipe_str += " audio-sink='%s' video-sink='%s'" % ( + afakesink, vfakesink) + + return pipe_str + + def _get_name(self, scenario, protocol, minfo): + return "%s.%s" % (self.get_fname(scenario, + protocol), + os.path.basename(minfo.media_descriptor.get_clean_name())) + + def populate_tests(self, uri_minfo_special_scenarios, scenarios): + test_rtsp = GstValidateBaseTestManager.RTSP_SERVER_COMMAND + if not test_rtsp: + printc("\n\nRTSP server not available, you should make sure" + " that %s is available in your $PATH." % GstValidateBaseTestManager.RTSP_SERVER_COMMAND, + Colors.FAIL) + elif self.test_manager.options.disable_rtsp: + printc("\n\nRTSP tests are disabled") + test_rtsp = False + + for uri, minfo, special_scenarios in uri_minfo_special_scenarios: + pipe = self._pipeline_template + protocol = minfo.media_descriptor.get_protocol() + + if protocol == Protocols.RTSP: + self.debug("SKIPPING %s as it is a RTSP stream" % uri) + continue + + pipe += " uri=%s" % uri + + for scenario in special_scenarios + scenarios: + cpipe = pipe + if not minfo.media_descriptor.is_compatible(scenario): + continue + + cpipe = self._set_sinks(minfo, cpipe, scenario) + fname = self._get_name(scenario, protocol, minfo) + + self.debug("Adding: %s", fname) + + if scenario.does_reverse_playback() and protocol == Protocols.HTTP: + # 10MB so we can reverse playback + cpipe += " ring-buffer-max-size=10485760" + + self.add_test(GstValidateLaunchTest(fname, + self.test_manager.options, + self.test_manager.reporter, + cpipe, + scenario=scenario, + media_descriptor=minfo.media_descriptor) + ) + + if test_rtsp and protocol == Protocols.FILE and not minfo.media_descriptor.is_image(): + rtspminfo = NamedDic({"path": minfo.media_descriptor.get_path(), + "media_descriptor": GstValidateRTSPMediaDescriptor(minfo.media_descriptor.get_path())}) + if not rtspminfo.media_descriptor.is_compatible(scenario): + continue + + cpipe = self._set_sinks(rtspminfo, "%s uri=rtsp://127.0.0.1:/test" + % self._pipeline_template, scenario) + fname = self._get_name(scenario, Protocols.RTSP, rtspminfo) + + self.add_test(GstValidateRTSPTest( + fname, self.test_manager.options, self.test_manager.reporter, + cpipe, uri, scenario=scenario, + media_descriptor=rtspminfo.media_descriptor)) + + fname = self._get_name(scenario, Protocols.RTSP + '2', rtspminfo) + self.add_test(GstValidateRTSPTest( + fname, self.test_manager.options, self.test_manager.reporter, + cpipe, uri, scenario=scenario, + media_descriptor=rtspminfo.media_descriptor, + rtsp2=True)) + + +class GstValidateCheckAccurateSeekingTestGenerator(GstValidatePipelineTestsGenerator): + def __new__(cls, name, test_manager, media_infos, extra_data=None): + pipelines = {} + + for path, reference_frame_dir in media_infos: + media_info = GstValidateMediaDescriptor(path) + media_info.set_protocol("file") + if not media_info: + error("GstValidateCheckAccurateSeekingTestGenerator", + "Could not create a media info file from %s" % path) + continue + + if media_info.is_image(): + error("GstValidateCheckAccurateSeekingTestGenerator", + "%s is an image, can't run accurate seeking tests" % path) + continue + + if media_info.get_num_tracks("video") < 1: + error("GstValidateCheckAccurateSeekingTestGenerator", + "%s is not a video, can't run accurate seeking tests" % path) + continue + + if media_info.get_num_tracks("video") < 1: + error("GstValidateCheckAccurateSeekingTestGenerator", + "No video track, can't run accurate seeking tests" % path) + continue + + if test_manager.options.validate_generate_ssim_reference_files: + scenario = None + test_name = media_info.get_clean_name() + '.generate_reference_files' + config = [ + 'validatessim, element-name="videoconvert", output-dir="%s"' % reference_frame_dir] + else: + test_name = media_info.get_clean_name() + framerate, scenario = cls.generate_scenario(test_manager.options, reference_frame_dir, media_info) + if scenario is None: + error("GstValidateCheckAccurateSeekingTestGenerator", + "Could not generate test for media info: %s" % path) + continue + + config = [ + '%(ssim)s, element-name="videoconvert", reference-images-dir="' + + reference_frame_dir + '", framerate=%d/%d' % (framerate.numerator, framerate.denominator) + ] + + pipelines[test_name] = { + "pipeline": "uridecodebin uri=" + media_info.get_uri() + " ! deinterlace ! videoconvert ! video/x-raw,interlace-mode=progressive,format=I420 ! videoconvert name=videoconvert ! %(videosink)s", + "media_info": media_info, + "config": config, + } + + if scenario: + pipelines[test_name]["scenarios"] = [scenario] + + return GstValidatePipelineTestsGenerator.from_dict(test_manager, name, pipelines, extra_data=extra_data) + + @classmethod + def generate_scenario(cls, options, reference_frame_dir, media_info): + actions = [ + "description, seek=true, handles-states=true, needs_preroll=true", + "pause", + ] + + framerate = None + for track_type, caps in media_info.get_tracks_caps(): + if track_type == 'video': + for struct, _ in GstCaps.new_from_str(caps): + framerate = struct["framerate"] + if framerate: + break + assert framerate + + n_frames = int((media_info.get_duration() * framerate.numerator) / (GST_SECOND * framerate.denominator)) + frames_timestamps = [math.ceil(i * framerate.denominator * GST_SECOND / framerate.numerator) for i in range(n_frames)] + # Ensure tests are not longer than long_limit, empirically considering we take 0.2 secs per frames. + acceptable_n_frames = options.long_limit * 5 + if n_frames > acceptable_n_frames: + n_frames_per_groups = int(acceptable_n_frames / 3) + frames_timestamps = frames_timestamps[0:n_frames_per_groups] \ + + frames_timestamps[int(n_frames / 2 - int(n_frames_per_groups / 2)):int(n_frames / 2 + int(n_frames_per_groups / 2))] \ + + frames_timestamps[-n_frames_per_groups:n_frames] + + actions += ['seek, flags=flush+accurate, start=(guint64)%s' % ts for ts in frames_timestamps] + actions += ['stop'] + + return framerate, { + "name": "check_accurate_seek", + "actions": actions, + } + + +class GstValidateMixerTestsGenerator(GstValidatePipelineTestsGenerator): + + def __init__(self, name, test_manager, mixer, media_type, converter="", + num_sources=3, mixed_srcs=None, valid_scenarios=None): + mixed_srcs = mixed_srcs or {} + valid_scenarios = valid_scenarios or [] + + pipe_template = "%(mixer)s name=_mixer ! " + \ + converter + " ! %(sink)s " + self.converter = converter + self.mixer = mixer + self.media_type = media_type + self.num_sources = num_sources + self.mixed_srcs = mixed_srcs + super( + GstValidateMixerTestsGenerator, self).__init__(name, test_manager, pipe_template, + valid_scenarios=valid_scenarios) + + def populate_tests(self, uri_minfo_special_scenarios, scenarios): + if self.test_manager.options.validate_uris: + return + + wanted_ressources = [] + for uri, minfo, special_scenarios in uri_minfo_special_scenarios: + protocol = minfo.media_descriptor.get_protocol() + if protocol == Protocols.FILE and \ + minfo.media_descriptor.get_num_tracks(self.media_type) > 0: + wanted_ressources.append((uri, minfo)) + + if not self.mixed_srcs: + if not wanted_ressources: + return + + for i in range(len(uri_minfo_special_scenarios) / self.num_sources): + srcs = [] + name = "" + for nsource in range(self.num_sources): + uri, minfo = wanted_ressources[i + nsource] + if os.getenv("USE_PLAYBIN3") is None: + srcs.append( + "uridecodebin uri=%s ! %s" % (uri, self.converter)) + else: + srcs.append( + "uridecodebin3 uri=%s ! %s" % (uri, self.converter)) + fname = os.path.basename(uri).replace(".", "_") + if not name: + name = fname + else: + name += "+%s" % fname + + self.mixed_srcs[name] = tuple(srcs) + + for name, srcs in self.mixed_srcs.items(): + if isinstance(srcs, dict): + pipe_arguments = { + "mixer": self.mixer + " %s" % srcs["mixer_props"]} + srcs = srcs["sources"] + else: + pipe_arguments = {"mixer": self.mixer} + + for scenario in scenarios: + fname = self.get_fname(scenario, Protocols.FILE) + "." + fname += name + + self.debug("Adding: %s", fname) + + if self.test_manager.options.mute: + pipe_arguments["sink"] = get_fakesink_for_media_type(self.media_type, + scenario.needs_clock_sync()) + else: + pipe_arguments["sink"] = "auto%ssink" % self.media_type + + pipe = self._pipeline_template % pipe_arguments + + for src in srcs: + pipe += "%s ! _mixer. " % src + + self.add_test(GstValidateLaunchTest(fname, + self.test_manager.options, + self.test_manager.reporter, + pipe, + scenario=scenario) + ) + + +class GstValidateSimpleTest(GstValidateTest): + def __init__(self, test_file, *args, **kwargs): + self.test_file = test_file + super().__init__(GstValidateBaseTestManager.COMMAND, *args, **kwargs) + + def build_arguments(self): + self.add_arguments('--set-test-file', self.test_file) + if self.options.mute: + self.add_arguments('--use-fakesinks') + + +class GstValidateLaunchTest(GstValidateTest): + + def __init__(self, classname, options, reporter, pipeline_desc, + timeout=DEFAULT_TIMEOUT, scenario=None, + media_descriptor=None, duration=0, hard_timeout=None, + extra_env_variables=None, expected_issues=None): + + self.extra_env_variables = extra_env_variables or {} + + if scenario: + duration = scenario.get_duration() + elif media_descriptor: + duration = media_descriptor.get_duration() / GST_SECOND + + super( + GstValidateLaunchTest, self).__init__(GstValidateBaseTestManager.COMMAND, + classname, + options, reporter, + duration=duration, + scenario=scenario, + timeout=timeout, + hard_timeout=hard_timeout, + media_descriptor=media_descriptor, + extra_env_variables=extra_env_variables, + expected_issues=expected_issues) + + self.pipeline_desc = pipeline_desc + self.media_descriptor = media_descriptor + + def build_arguments(self): + GstValidateTest.build_arguments(self) + self.add_arguments(*shlex.split(self.pipeline_desc)) + if self.media_descriptor is not None and self.media_descriptor.get_path(): + self.add_arguments( + "--set-media-info", self.media_descriptor.get_path()) + + +class GstValidateMediaCheckTest(GstValidateTest): + + def __init__(self, classname, options, reporter, media_descriptor, + uri, minfo_path, timeout=DEFAULT_TIMEOUT, + extra_env_variables=None, + expected_issues=None): + self.extra_env_variables = extra_env_variables or {} + + super( + GstValidateMediaCheckTest, self).__init__(GstValidateBaseTestManager.MEDIA_CHECK_COMMAND, classname, + options, reporter, + timeout=timeout, + media_descriptor=media_descriptor, + extra_env_variables=extra_env_variables, + expected_issues=expected_issues) + self._uri = uri + self._media_info_path = minfo_path + + def build_arguments(self): + Test.build_arguments(self) + self.add_arguments(self._uri, "--expected-results", + self._media_info_path) + + if self.media_descriptor.skip_parsers(): + self.add_arguments("--skip-parsers") + + +class GstValidateTranscodingTest(GstValidateTest, GstValidateEncodingTestInterface): + scenarios_manager = ScenarioManager() + + def __init__(self, classname, options, reporter, + combination, uri, media_descriptor, + timeout=DEFAULT_TIMEOUT, + scenario=None, + extra_env_variables=None, + expected_issues=None): + Loggable.__init__(self) + + self.extra_env_variables = extra_env_variables or {} + + file_dur = int(media_descriptor.get_duration()) / GST_SECOND + if not media_descriptor.get_num_tracks("video"): + self.debug("%s audio only file applying transcoding ratio." + "File 'duration' : %s" % (classname, file_dur)) + duration = file_dur / AUDIO_ONLY_FILE_TRANSCODING_RATIO + else: + duration = file_dur + + super( + GstValidateTranscodingTest, self).__init__(GstValidateBaseTestManager.TRANSCODING_COMMAND, + classname, + options, + reporter, + duration=duration, + timeout=timeout, + scenario=scenario, + media_descriptor=media_descriptor, + extra_env_variables=None, + expected_issues=expected_issues) + extra_env_variables = extra_env_variables or {} + + GstValidateEncodingTestInterface.__init__( + self, combination, media_descriptor) + + self.uri = uri + + def run_external_checks(self): + if self.media_descriptor.get_num_tracks("video") == 1 and \ + self.options.validate_enable_iqa_tests: + self.run_iqa_test(self.uri) + + def set_rendering_info(self): + self.dest_file = os.path.join(self.options.dest, + self.classname.replace(".transcode.", os.sep). + replace(".", os.sep)) + mkdir(os.path.dirname(urllib.parse.urlsplit(self.dest_file).path)) + if urllib.parse.urlparse(self.dest_file).scheme == "": + self.dest_file = path2url(self.dest_file) + + profile = self.get_profile() + self.add_arguments("-o", profile) + + def build_arguments(self): + GstValidateTest.build_arguments(self) + self.set_rendering_info() + self.add_arguments(self.uri, self.dest_file) + + def get_current_value(self): + if self.scenario: + sent_eos = self.sent_eos_position() + if sent_eos is not None: + t = time.time() + if ((t - sent_eos)) > 30: + if self.media_descriptor.get_protocol() == Protocols.HLS: + self.set_result(Result.PASSED, + """Got no EOS 30 seconds after sending EOS, + in HLS known and tolerated issue: + https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/issues/132""") + return Result.KNOWN_ERROR + + self.set_result( + Result.FAILED, "Pipeline did not stop 30 Seconds after sending EOS") + + return Result.FAILED + + size = self.get_current_size() + if size is None: + return self.get_current_position() + + return size + + def check_results(self): + self.check_encoded_file() + GstValidateTest.check_results(self) + + +class GstValidateBaseRTSPTest: + """ Interface for RTSP tests, requires implementing Test""" + __used_ports = set() + + def __init__(self, local_uri): + self._local_uri = local_uri + self.rtsp_server = None + self._unsetport_pipeline_desc = None + self.optional = True + + @classmethod + def __get_open_port(cls): + while True: + # hackish trick from + # http://stackoverflow.com/questions/2838244/get-open-tcp-port-in-python?answertab=votes#tab-top + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.bind(("", 0)) + port = s.getsockname()[1] + if port not in cls.__used_ports: + cls.__used_ports.add(port) + s.close() + return port + + s.close() + + def launch_server(self): + if self.options.redirect_logs == 'stdout': + self.rtspserver_logs = sys.stdout + elif self.options.redirect_logs == 'stderr': + self.rtspserver_logs = sys.stderr + + self.server_port = self.__get_open_port() + command = [GstValidateBaseTestManager.RTSP_SERVER_COMMAND, self._local_uri, '--port', str(self.server_port)] + + if self.options.validate_gdb_server: + command = self.use_gdb(command) + self.rtspserver_logs = sys.stdout + elif self.options.redirect_logs: + self.rtspserver_logs = sys.stdout + else: + self.rtspserver_logs = open(self.logfile + '_rtspserver.log', 'w+') + self.extra_logfiles.add(self.rtspserver_logs.name) + + server_env = os.environ.copy() + + self.rtsp_server = subprocess.Popen(command, + stderr=self.rtspserver_logs, + stdout=self.rtspserver_logs, + env=server_env) + while True: + s = socket.socket() + try: + s.connect((("127.0.0.1", self.server_port))) + break + except ConnectionRefusedError: + time.sleep(0.1) + continue + finally: + s.close() + + if not self._unsetport_pipeline_desc: + self._unsetport_pipeline_desc = self.pipeline_desc + + self.pipeline_desc = self._unsetport_pipeline_desc.replace( + "", str(self.server_port)) + + return ' '.join(command) + + def close_logfile(self): + super().close_logfile() + if not self.options.redirect_logs: + self.rtspserver_logs.close() + + def process_update(self): + res = super().process_update() + if res: + kill_subprocess(self, self.rtsp_server, DEFAULT_TIMEOUT) + self.__used_ports.remove(self.server_port) + + return res + + +class GstValidateRTSPTest(GstValidateBaseRTSPTest, GstValidateLaunchTest): + + def __init__(self, classname, options, reporter, pipeline_desc, + local_uri, timeout=DEFAULT_TIMEOUT, scenario=None, + media_descriptor=None, rtsp2=False): + GstValidateLaunchTest.__init__(self, classname, options, reporter, + pipeline_desc, timeout, scenario, + media_descriptor) + GstValidateBaseRTSPTest.__init__(self, local_uri) + self.rtsp2 = rtsp2 + + def get_subproc_env(self): + env = super().get_subproc_env() + path = env.get('GST_VALIDATE_SCENARIOS_PATH', '') + override_dir = get_data_file(os.path.join('data', 'scenarios'), 'rtsp_overrides') + env['GST_VALIDATE_SCENARIOS_PATH'] = '%s:%s' % (override_dir, path) + if self.rtsp2: + env['GST_VALIDATE_SCENARIO'] = env.get('GST_VALIDATE_SCENARIO', '') + ':' + 'force_rtsp2' + + return env + + +class GstValidateRTSPMediaDescriptor(GstValidateMediaDescriptor): + + def __init__(self, xml_path): + GstValidateMediaDescriptor.__init__(self, xml_path) + + def get_uri(self): + return "rtsp://127.0.0.1:8554/test" + + def get_protocol(self): + return Protocols.RTSP + + def prerrols(self): + return False + + +class GstValidateTestManager(GstValidateBaseTestManager): + + name = "validate" + + # List of all classes to create testsuites + GstValidateMediaCheckTestsGenerator = GstValidateMediaCheckTestsGenerator + GstValidateTranscodingTestsGenerator = GstValidateTranscodingTestsGenerator + GstValidatePipelineTestsGenerator = GstValidatePipelineTestsGenerator + GstValidatePlaybinTestsGenerator = GstValidatePlaybinTestsGenerator + GstValidateMixerTestsGenerator = GstValidateMixerTestsGenerator + GstValidateCheckAccurateSeekingTestGenerator = GstValidateCheckAccurateSeekingTestGenerator + GstValidateLaunchTest = GstValidateLaunchTest + GstValidateMediaCheckTest = GstValidateMediaCheckTest + GstValidateTranscodingTest = GstValidateTranscodingTest + + def __init__(self): + super(GstValidateTestManager, self).__init__() + self._uris = [] + self._run_defaults = True + self._is_populated = False + self._default_generators_registered = False + + def init(self): + for command, name in [ + (GstValidateBaseTestManager.TRANSCODING_COMMAND, "gst-validate-transcoding-1.0"), + (GstValidateBaseTestManager.COMMAND, "gst-validate-1.0"), + (GstValidateBaseTestManager.MEDIA_CHECK_COMMAND, "gst-validate-media-check-1.0")]: + if not command: + self.error("command not found: %s" % name) + return False + + return True + + def add_options(self, parser): + group = parser.add_argument_group("GstValidate tools specific options" + " and behaviours", + description="""When using --wanted-tests, all the scenarios can be used, even those which have +not been tested and explicitly activated if you set use --wanted-tests ALL""") + group.add_argument("--validate-check-uri", dest="validate_uris", + action="append", help="defines the uris to run default tests on") + group.add_argument("--validate-tools-path", dest="validate_tools_path", + action="append", help="defines the paths to look for GstValidate tools.") + group.add_argument("--validate-gdb-server", dest="validate_gdb_server", + help="Run the server in GDB.") + group.add_argument("--validate-disable-rtsp", dest="disable_rtsp", + help="Disable RTSP tests.", default=False, action='store_true') + group.add_argument("--validate-enable-iqa-tests", dest="validate_enable_iqa_tests", + help="Enable Image Quality Assessment validation tests.", + default=False, action='store_true') + group.add_argument("--validate-generate-expectations", dest="validate_generate_expectations", + choices=['auto', 'enabled', 'disabled'], + help="Force generating expectations (when set to `enabed`)" + " force failure on missing expactations when set to `disabled`" + " and create if needed when set to `auto`.", + default='auto') + group.add_argument("--validate-generate-ssim-reference-files", + help="(re)generate ssim reference image files.", + default=False, action='store_true') + + def print_valgrind_bugs(self): + # Look for all the 'pending' bugs in our supp file + bugs = [] + p = get_data_file('data', 'gstvalidate.supp') + with open(p) as f: + for line in f.readlines(): + line = line.strip() + if line.startswith('# PENDING:'): + tmp = line.split(' ') + bugs.append(tmp[2]) + + if bugs: + msg = "Ignored valgrind bugs:\n" + for b in bugs: + msg += " + %s\n" % b + printc(msg, Colors.FAIL, True) + + def populate_testsuite(self): + + if self._is_populated is True: + return + + if not self.options.config and not self.options.testsuites: + if self._run_defaults: + self.register_defaults() + else: + self.register_all() + + self._is_populated = True + + def list_tests(self): + if self.tests: + return self.tests + + if self._run_defaults: + scenarios = [self.scenarios_manager.get_scenario(scenario_name) + for scenario_name in self.get_scenarios()] + else: + scenarios = self.scenarios_manager.get_scenario(None) + uris = self._list_uris() + + for generator in self.get_generators(): + for test in generator.generate_tests(uris, scenarios): + self.add_test(test) + + if not self.tests and not uris and not self.options.wanted_tests: + self.info("No valid uris present in the path. Check if media files and info files exist") + + return self.tests + + def _add_media(self, media_info, uri=None): + self.debug("Checking %s", media_info) + if isinstance(media_info, GstValidateMediaDescriptor): + media_descriptor = media_info + media_info = media_descriptor.get_path() + else: + media_descriptor = GstValidateMediaDescriptor(media_info) + + try: + # Just testing that the various mandatory infos are present + caps = media_descriptor.get_caps() + if uri is None or media_descriptor.get_protocol() == Protocols.IMAGESEQUENCE: + uri = media_descriptor.get_uri() + + # Adjust local http uri + if self.options.http_server_port != 8079 and \ + uri.startswith("http://127.0.0.1:8079/"): + uri = uri.replace("http://127.0.0.1:8079/", + "http://127.0.0.1:%r/" % self.options.http_server_port, 1) + media_descriptor.set_protocol(urllib.parse.urlparse(uri).scheme) + for caps2, prot in GST_VALIDATE_CAPS_TO_PROTOCOL: + if caps2 == caps: + media_descriptor.set_protocol(prot) + break + + scenario_bname = media_descriptor.get_media_filepath() + special_scenarios = self.scenarios_manager.find_special_scenarios( + scenario_bname) + self._uris.append((uri, + NamedDic({"path": media_info, + "media_descriptor": media_descriptor}), + special_scenarios)) + except configparser.NoOptionError as e: + self.debug("Exception: %s for %s", e, media_info) + + def _discover_file(self, uri, fpath): + for ext in (GstValidateMediaDescriptor.MEDIA_INFO_EXT, + GstValidateMediaDescriptor.PUSH_MEDIA_INFO_EXT, + GstValidateMediaDescriptor.SKIPPED_MEDIA_INFO_EXT): + try: + is_push = ext == GstValidateMediaDescriptor.PUSH_MEDIA_INFO_EXT + is_skipped = ext == GstValidateMediaDescriptor.SKIPPED_MEDIA_INFO_EXT + media_info = "%s.%s" % (fpath, ext) + if is_push or is_skipped: + if not os.path.exists(media_info): + continue + if is_push: + uri = "push" + uri + args = GstValidateBaseTestManager.MEDIA_CHECK_COMMAND.split(" ") + args.append(uri) + if os.path.isfile(media_info) and not self.options.update_media_info and not is_skipped: + self._add_media(media_info, uri) + continue + elif fpath.endswith(GstValidateMediaDescriptor.STREAM_INFO_EXT) and not is_skipped: + self._add_media(fpath) + continue + elif not self.options.generate_info and not self.options.update_media_info and not self.options.validate_uris: + continue + elif self.options.update_media_info and not os.path.isfile(media_info): + self.info( + "%s not present. Use --generate-media-info", media_info) + continue + elif os.path.islink(media_info): + self.info( + "%s is a symlink, not updating and hopefully the actual file gets updated!", media_info) + continue + + include_frames = 0 + if self.options.update_media_info: + include_frames = 2 + elif self.options.generate_info_full: + include_frames = 1 + + media_descriptor = GstValidateMediaDescriptor.new_from_uri( + uri, True, include_frames, is_push, is_skipped) + if media_descriptor: + self._add_media(media_descriptor, uri) + else: + self.warning("Could not get any descriptor for %s" % uri) + + except subprocess.CalledProcessError as e: + if self.options.generate_info: + printc("Result: Failed", Colors.FAIL) + else: + self.error("Exception: %s", e) + return False + return True + + def _list_uris(self): + if self._uris: + return self._uris + + if self.options.validate_uris: + for uri in self.options.validate_uris: + self._discover_file(uri, uri) + return self._uris + + if not self.args: + if isinstance(self.options.paths, str): + self.options.paths = [os.path.join(self.options.paths)] + + for path in self.options.paths: + if os.path.isfile(path): + path = os.path.abspath(path) + self._discover_file(path2url(path), path) + else: + for root, dirs, files in os.walk(path): + for f in files: + fpath = os.path.abspath(os.path.join(root, f)) + if os.path.isdir(fpath) or \ + fpath.endswith(GstValidateMediaDescriptor.MEDIA_INFO_EXT) or\ + fpath.endswith(ScenarioManager.FILE_EXTENSION): + continue + else: + self._discover_file(path2url(fpath), fpath) + + self.debug("Uris found: %s", self._uris) + + return self._uris + + def needs_http_server(self): + for test in self.list_tests(): + if self._is_test_wanted(test) and test.media_descriptor is not None: + protocol = test.media_descriptor.get_protocol() + uri = test.media_descriptor.get_uri() + uri_requires_http_server = False + if uri: + if 'http-server-port' in uri: + expanded_uri = uri % { + 'http-server-port': self.options.http_server_port} + uri_requires_http_server = expanded_uri.find( + "127.0.0.1:%s" % self.options.http_server_port) != -1 + if protocol in [Protocols.HTTP, Protocols.HLS, Protocols.DASH] or uri_requires_http_server: + return True + return False + + def set_settings(self, options, args, reporter): + if options.wanted_tests: + for i in range(len(options.wanted_tests)): + if "ALL" in options.wanted_tests[i]: + self._run_defaults = False + options.wanted_tests[ + i] = options.wanted_tests[i].replace("ALL", "") + + options.validate_default_config = None + if options.validate_generate_expectations != 'auto': + options.validate_default_config = os.path.join(options.logsdir, "__validate_default.config") + with open(options.validate_default_config, 'w') as f: + val = "true" if options.validate_generate_expectations == "enabled" else "false" + print("validateflow,generate-expectations=%s" % val, file=f) + try: + options.wanted_tests.remove("") + except ValueError: + pass + + if options.validate_uris or options.validate_generate_ssim_reference_files: + self.check_testslist = False + + super(GstValidateTestManager, self).set_settings( + options, args, reporter) + + def register_defaults(self): + """ + Registers the defaults: + * Scenarios to be used + * Encoding formats to be used + * Blacklisted tests + * Test generators + """ + printc("-> Registering default 'validate' tests... ", end='') + self.register_default_scenarios() + self.register_default_encoding_formats() + self.register_default_blacklist() + self.register_default_test_generators() + printc("OK", Colors.OKGREEN) + + def register_default_scenarios(self): + """ + Registers default test scenarios + """ + if self.options.long_limit != 0: + self.add_scenarios([ + "play_15s", + "reverse_playback", + "fast_forward", + "seek_forward", + "seek_backward", + "seek_with_stop", + "switch_audio_track", + "switch_audio_track_while_paused", + "switch_subtitle_track", + "switch_subtitle_track_while_paused", + "disable_subtitle_track_while_paused", + "change_state_intensive", + "scrub_forward_seeking"]) + else: + self.add_scenarios([ + "play_15s", + "reverse_playback", + "fast_forward", + "seek_forward", + "seek_backward", + "seek_with_stop", + "switch_audio_track", + "switch_audio_track_while_paused", + "switch_subtitle_track", + "switch_subtitle_track_while_paused", + "disable_subtitle_track_while_paused", + "change_state_intensive", + "scrub_forward_seeking"]) + + def register_default_encoding_formats(self): + """ + Registers default encoding formats + """ + self.add_encoding_formats([ + MediaFormatCombination("ogg", "vorbis", "theora"), + MediaFormatCombination("webm", "vorbis", "vp8"), + MediaFormatCombination("webm", "vorbis", "vp9", + video_restriction="video/x-raw,width=160,height=120"), + MediaFormatCombination("mp4", "mp3", "h264"), + MediaFormatCombination("mkv", "vorbis", "h264"), + ]) + + def register_default_blacklist(self): + self.set_default_blacklist([ + # testbin known issues + ("testbin.media_check.*", + "Not supported by GstDiscoverer."), + + # dash known issues + ("dash.media_check.*", + "Caps are different depending on selected bitrates, etc"), + + # Matroska/WEBM known issues: + ("*.reverse_playback.*webm$", + "https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/issues/65"), + ("*.reverse_playback.*mkv$", + "https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/issues/65"), + ("http.playback.seek_with_stop.*webm", + "matroskademux.gst_matroska_demux_handle_seek_push: Seek end-time not supported in streaming mode"), + ("http.playback.seek_with_stop.*mkv", + "matroskademux.gst_matroska_demux_handle_seek_push: Seek end-time not supported in streaming mode"), + + # MPEG TS known issues: + ('(?i)*playback.reverse_playback.*(?:_|.)(?:|m)ts$', + "https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/issues/97"), + + # Fragmented MP4 disabled tests: + ('*.playback..*seek.*.fragmented_nonseekable_sink_mp4', + "Seeking on fragmented files without indexes isn't implemented"), + ('*.playback.reverse_playback.fragmented_nonseekable_sink_mp4', + "Seeking on fragmented files without indexes isn't implemented"), + + # HTTP known issues: + ("http.*scrub_forward_seeking.*", + "This is not stable enough for now."), + ("http.playback.change_state_intensive.raw_video_mov", + "This is not stable enough for now. (flow return from pad push doesn't match expected value)"), + + # MXF known issues" + ("*reverse_playback.*mxf", + "Reverse playback is not handled in MXF"), + ("file\.transcode.*mxf", + "FIXME: Transcoding and mixing tests need to be tested"), + + # WMV known issues" + ("*reverse_playback.*wmv", + "Reverse playback is not handled in wmv"), + (".*reverse_playback.*asf", + "Reverse playback is not handled in asf"), + + # ogg known issues + ("http.playback.seek.*vorbis_theora_1_ogg", + "https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/issues/281"), + # RTSP known issues + ('rtsp.*playback.reverse.*', + 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/issues/32'), + ('rtsp.*playback.seek_with_stop.*', + 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/issues/386'), + ('rtsp.*playback.fast_*', + 'rtpbasedepayload does not handle rate != 1.0 correctly'), + ]) + + def register_default_test_generators(self): + """ + Registers default test generators + """ + if self._default_generators_registered: + return + + self.add_generators([GstValidatePlaybinTestsGenerator(self), + GstValidateMediaCheckTestsGenerator(self), + GstValidateTranscodingTestsGenerator(self)]) + self._default_generators_registered = True diff --git a/validate/launcher/apps/meson.build b/validate/launcher/apps/meson.build new file mode 100644 index 0000000000..72f2f7fbd1 --- /dev/null +++ b/validate/launcher/apps/meson.build @@ -0,0 +1,2 @@ +install_data(sources: ['__init__.py', 'gstvalidate.py', 'gstcheck.py', 'pyunittest.py'], + install_dir: _launcherdir + '/apps') diff --git a/validate/launcher/apps/pyunittest.py b/validate/launcher/apps/pyunittest.py new file mode 100644 index 0000000000..b97f6b5220 --- /dev/null +++ b/validate/launcher/apps/pyunittest.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2018,Thibault Saunier +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. +import os +import sys +import unittest + +from launcher.baseclasses import Test +from launcher.baseclasses import TestsManager + + +class PythonTest(Test): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._testname = self.classname + + def build_arguments(self): + """Builds subprocess arguments.""" + self.add_arguments('-m', 'unittest', self._testname) + + +class PythonTestsManager(TestsManager): + name = "pyunittest" + arggroup = None + + def __init__(self): + super().__init__() + + def add_options(self, parser): + if self.arggroup: + return + + arggroup = PythonTestsManager.arggroup = parser.add_argument_group( + "Python tests specific options and behaviours") + arggroup.add_argument("--pyunittest-dir", + action="append", + default=[], + help="Paths to look for Python tests.") + + def list_tests(self): + if self.tests: + return self.tests + + for _dir in self.options.pyunittest_dir: + loader = unittest.TestLoader() + testsuites = loader.discover(_dir) + for testsuite in testsuites: + for _tests in testsuite: + if isinstance(_tests, unittest.loader._FailedTest): + raise(_tests._exception) + for test in _tests: + self.add_test(PythonTest( + sys.executable, test.id(), + self.options, self.reporter, + extra_env_variables={'PYTHONPATH': _dir})) + + return self.tests diff --git a/validate/launcher/baseclasses.py b/validate/launcher/baseclasses.py new file mode 100644 index 0000000000..5f9f29f9f9 --- /dev/null +++ b/validate/launcher/baseclasses.py @@ -0,0 +1,2873 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2013,Thibault Saunier +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +""" Class representing tests and test managers. """ + +import importlib.util +import json +import os +import sys +import re +import copy +import shlex +import socketserver +import struct +import time +from . import utils +import signal +import urllib.parse +import subprocess +import threading +import queue +import configparser +import xml +import random +import shutil +import uuid +from itertools import cycle +from fractions import Fraction + +from .utils import which +from . import reporters +from . import loggable +from .loggable import Loggable + +from collections import defaultdict +try: + from lxml import etree as ET +except ImportError: + import xml.etree.cElementTree as ET + + +from .vfb_server import get_virual_frame_buffer_server +from .httpserver import HTTPServer +from .utils import mkdir, Result, Colors, printc, DEFAULT_TIMEOUT, GST_SECOND, \ + Protocols, look_for_file_in_source_dir, get_data_file, BackTraceGenerator, \ + check_bugs_resolution, is_tty + +# The factor by which we increase the hard timeout when running inside +# Valgrind +GDB_TIMEOUT_FACTOR = VALGRIND_TIMEOUT_FACTOR = 20 +RR_TIMEOUT_FACTOR = 2 +TIMEOUT_FACTOR = float(os.environ.get("TIMEOUT_FACTOR", 1)) +# The error reported by valgrind when detecting errors +VALGRIND_ERROR_CODE = 20 + +VALIDATE_OVERRIDE_EXTENSION = ".override" +EXITING_SIGNALS = dict([(-getattr(signal, s), s) for s in [ + 'SIGQUIT', 'SIGILL', 'SIGABRT', 'SIGFPE', 'SIGSEGV', 'SIGBUS', 'SIGSYS', + 'SIGTRAP', 'SIGXCPU', 'SIGXFSZ', 'SIGIOT'] if hasattr(signal, s)]) +EXITING_SIGNALS.update({139: "SIGSEGV"}) +EXITING_SIGNALS.update({(v, k) for k, v in EXITING_SIGNALS.items()}) + + +CI_ARTIFACTS_URL = os.environ.get('CI_ARTIFACTS_URL') + + +class Test(Loggable): + + """ A class representing a particular test. """ + + def __init__(self, application_name, classname, options, + reporter, duration=0, timeout=DEFAULT_TIMEOUT, + hard_timeout=None, extra_env_variables=None, + expected_issues=None, is_parallel=True, + workdir=None): + """ + @timeout: The timeout during which the value return by get_current_value + keeps being exactly equal + @hard_timeout: Max time the test can take in absolute + """ + Loggable.__init__(self) + self.timeout = timeout * TIMEOUT_FACTOR * options.timeout_factor + if hard_timeout: + self.hard_timeout = hard_timeout * TIMEOUT_FACTOR + self.hard_timeout *= options.timeout_factor + else: + self.hard_timeout = hard_timeout + self.classname = classname + self.options = options + self.application = application_name + self.command = [] + self.server_command = None + self.reporter = reporter + self.process = None + self.proc_env = None + self.thread = None + self.queue = None + self.duration = duration + self.stack_trace = None + self._uuid = None + if expected_issues is None: + self.expected_issues = [] + elif not isinstance(expected_issues, list): + self.expected_issues = [expected_issues] + else: + self.expected_issues = expected_issues + + extra_env_variables = extra_env_variables or {} + self.extra_env_variables = extra_env_variables + self.optional = False + self.is_parallel = is_parallel + self.generator = None + self.workdir = workdir + self.allow_flakiness = False + self.html_log = None + self.rr_logdir = None + + self.clean() + + def _generate_expected_issues(self): + return '' + + def generate_expected_issues(self): + res = '%s"FIXME \'%s\' issues [REPORT A BUG ' % (" " * 4, self.classname) \ + + 'in https://gitlab.freedesktop.org/gstreamer/ '\ + + 'or use a proper bug description]": {' + res += """ + "tests": [ + "%s" + ], + "issues": [""" % (self.classname) + + retcode = self.process.returncode if self.process else 0 + if retcode != 0: + signame = EXITING_SIGNALS.get(retcode) + val = "'" + signame + "'" if signame else retcode + res += """\n { + '%s': %s, + 'sometimes': True, + },""" % ("signame" if signame else "returncode", val) + + res += self._generate_expected_issues() + res += "\n%s],\n%s},\n" % (" " * 8, " " * 4) + + return res + + def copy(self, nth=None): + copied_test = copy.copy(self) + if nth: + copied_test.classname += '_it' + str(nth) + copied_test.options = copy.copy(self.options) + copied_test.options.logsdir = os.path.join(copied_test.options.logsdir, str(nth)) + os.makedirs(copied_test.options.logsdir, exist_ok=True) + + return copied_test + + def clean(self): + self.kill_subprocess() + self.message = "" + self.error_str = "" + self.time_taken = 0.0 + self._starting_time = None + self.result = Result.NOT_RUN + self.logfile = None + self.out = None + self.extra_logfiles = set() + self.__env_variable = [] + self.kill_subprocess() + self.process = None + + def __str__(self): + string = self.classname + if self.result != Result.NOT_RUN: + string += ": " + self.result + if self.result in [Result.FAILED, Result.TIMEOUT]: + string += " '%s'" % self.message + if not self.options.dump_on_failure: + if not self.options.redirect_logs and self.result != Result.PASSED: + string += self.get_logfile_repr() + else: + string = "\n==> %s" % string + + return string + + def add_env_variable(self, variable, value=None): + """ + Only useful so that the gst-validate-launcher can print the exact + right command line to reproduce the tests + """ + if value is None: + value = os.environ.get(variable, None) + + if value is None: + return + + self.__env_variable.append(variable) + + @property + def _env_variable(self): + res = "" + if not self.options.verbose or self.options.verbose > 1: + for var in set(self.__env_variable): + if res: + res += " " + value = self.proc_env.get(var, None) + if value is not None: + res += "%s='%s'" % (var, value) + else: + res += "[Not displaying environment variables, rerun with -vv for the full command]" + + return res + + def open_logfile(self): + if self.out: + return + + path = os.path.join(self.options.logsdir, + self.classname.replace(".", os.sep) + '.md') + mkdir(os.path.dirname(path)) + self.logfile = path + + if self.options.redirect_logs == 'stdout': + self.out = sys.stdout + elif self.options.redirect_logs == 'stderr': + self.out = sys.stderr + else: + self.out = open(path, 'w+') + + def finalize_logfiles(self): + self.out.write("\n**Duration**: %s" % self.time_taken) + if not self.options.redirect_logs: + self.out.flush() + for logfile in self.extra_logfiles: + # Only copy over extra logfile content if it's below a certain threshold + # Avoid copying gigabytes of data if a lot of debugging is activated + if os.path.getsize(logfile) < 500 * 1024: + self.out.write('\n\n## %s:\n\n```\n%s\n```\n' % ( + os.path.basename(logfile), self.get_extra_log_content(logfile)) + ) + else: + self.out.write('\n\n## %s:\n\n**Log file too big.**\n %s\n\n Check file content directly\n\n' % ( + os.path.basename(logfile), logfile) + ) + + if self.rr_logdir: + self.out.write('\n\n## rr trace:\n\n```\nrr replay %s/latest-trace\n```\n' % ( + self.rr_logdir)) + + self.out.flush() + self.out.close() + + if self.options.html: + self.html_log = os.path.splitext(self.logfile)[0] + '.html' + import commonmark + parser = commonmark.Parser() + with open(self.logfile) as f: + ast = parser.parse(f.read()) + + renderer = commonmark.HtmlRenderer() + html = renderer.render(ast) + with open(self.html_log, 'w') as f: + f.write(html) + + self.out = None + + def _get_file_content(self, file_name): + f = open(file_name, 'r+') + value = f.read() + f.close() + + return value + + def get_log_content(self): + return self._get_file_content(self.logfile) + + def get_extra_log_content(self, extralog): + if extralog not in self.extra_logfiles: + return "" + + return self._get_file_content(extralog) + + def get_classname(self): + name = self.classname.split('.')[-1] + classname = self.classname.replace('.%s' % name, '') + + return classname + + def get_name(self): + return self.classname.split('.')[-1] + + def get_uuid(self): + if self._uuid is None: + self._uuid = self.classname + str(uuid.uuid4()) + return self._uuid + + def add_arguments(self, *args): + self.command += args + + def build_arguments(self): + self.add_env_variable("LD_PRELOAD") + self.add_env_variable("DISPLAY") + + def add_stack_trace_to_logfile(self): + self.debug("Adding stack trace") + if self.options.rr: + return + + trace_gatherer = BackTraceGenerator.get_default() + stack_trace = trace_gatherer.get_trace(self) + + if not stack_trace: + return + + info = "\n\n## Stack trace\n\n```\n%s\n```" % stack_trace + if self.options.redirect_logs: + print(info) + return + + if self.options.xunit_file: + self.stack_trace = stack_trace + + self.out.write(info) + self.out.flush() + + def add_known_issue_information(self): + if self.expected_issues: + info = "\n\n## Already known issues\n\n``` python\n%s\n```\n\n" % ( + json.dumps(self.expected_issues, indent=4) + ) + else: + info = "" + + info += "\n\n**You can mark the issues as 'known' by adding the " \ + + " following lines to the list of known issues**\n" \ + + "\n\n``` python\n%s\n```" % (self.generate_expected_issues()) + + if self.options.redirect_logs: + print(info) + return + + self.out.write(info) + + def set_result(self, result, message="", error=""): + + if not self.options.redirect_logs: + self.out.write("\n```\n") + self.out.flush() + + self.debug("Setting result: %s (message: %s, error: %s)" % (result, + message, error)) + + if result is Result.TIMEOUT: + if self.options.debug is True: + if self.options.gdb: + printc("Timeout, you should process c to get into gdb", + Colors.FAIL) + # and wait here until gdb exits + self.process.communicate() + else: + pname = self.command[0] + input("%sTimeout happened on %s you can attach gdb doing:\n $gdb %s %d%s\n" + "Press enter to continue" % (Colors.FAIL, self.classname, + pname, self.process.pid, Colors.ENDC)) + else: + self.add_stack_trace_to_logfile() + + self.result = result + self.message = message + self.error_str = error + + if result not in [Result.PASSED, Result.NOT_RUN, Result.SKIPPED]: + self.add_known_issue_information() + + def check_results(self): + if self.result is Result.FAILED or self.result is Result.TIMEOUT: + return + + self.debug("%s returncode: %s", self, self.process.returncode) + if self.options.rr and self.process.returncode == -signal.SIGPIPE: + self.set_result(Result.SKIPPED, "SIGPIPE received under `rr`, known issue.") + elif self.process.returncode == 0: + self.set_result(Result.PASSED) + elif self.process.returncode in EXITING_SIGNALS: + self.add_stack_trace_to_logfile() + self.set_result(Result.FAILED, + "Application exited with signal %s" % ( + EXITING_SIGNALS[self.process.returncode])) + elif self.process.returncode == VALGRIND_ERROR_CODE: + self.set_result(Result.FAILED, "Valgrind reported errors") + else: + self.set_result(Result.FAILED, + "Application returned %d" % (self.process.returncode)) + + def get_current_value(self): + """ + Lets subclasses implement a nicer timeout measurement method + They should return some value with which we will compare + the previous and timeout if they are egual during self.timeout + seconds + """ + return Result.NOT_RUN + + def process_update(self): + """ + Returns True when process has finished running or has timed out. + """ + + if self.process is None: + # Process has not started running yet + return False + + self.process.poll() + if self.process.returncode is not None: + return True + + val = self.get_current_value() + + self.debug("Got value: %s" % val) + if val is Result.NOT_RUN: + # The get_current_value logic is not implemented... dumb + # timeout + if time.time() - self.last_change_ts > self.timeout: + self.set_result(Result.TIMEOUT, + "Application timed out: %s secs" % + self.timeout, + "timeout") + return True + return False + elif val is Result.FAILED: + return True + elif val is Result.KNOWN_ERROR: + return True + + self.log("New val %s" % val) + + if val == self.last_val: + delta = time.time() - self.last_change_ts + self.debug("%s: Same value for %d/%d seconds" % + (self, delta, self.timeout)) + if delta > self.timeout: + self.set_result(Result.TIMEOUT, + "Application timed out: %s secs" % + self.timeout, + "timeout") + return True + elif self.hard_timeout and time.time() - self.start_ts > self.hard_timeout: + self.set_result( + Result.TIMEOUT, "Hard timeout reached: %d secs" % self.hard_timeout) + return True + else: + self.last_change_ts = time.time() + self.last_val = val + + return False + + def get_subproc_env(self): + return os.environ.copy() + + def kill_subprocess(self): + subprocs_id = None + if self.options.rr and self.process and self.process.returncode is None: + cmd = ["ps", "-o", "pid", "--ppid", str(self.process.pid), "--noheaders"] + try: + subprocs_id = [int(pid.strip('\n')) for + pid in subprocess.check_output(cmd).decode().split(' ') if pid] + except FileNotFoundError: + self.error("Ps not found, will probably not be able to get rr " + "working properly after we kill the process") + except subprocess.CalledProcessError as e: + self.error("Couldn't get rr subprocess pid: %s" % (e)) + + utils.kill_subprocess(self, self.process, DEFAULT_TIMEOUT, subprocs_id) + + def run_external_checks(self): + pass + + def thread_wrapper(self): + def enable_sigint(): + # Restore the SIGINT handler for the child process (gdb) to ensure + # it can handle it. + signal.signal(signal.SIGINT, signal.SIG_DFL) + + if self.options.gdb and os.name != "nt": + preexec_fn = enable_sigint + else: + preexec_fn = None + + self.process = subprocess.Popen(self.command, + stderr=self.out, + stdout=self.out, + env=self.proc_env, + cwd=self.workdir, + preexec_fn=preexec_fn) + self.process.wait() + if self.result is not Result.TIMEOUT: + if self.process.returncode == 0: + self.run_external_checks() + self.queue.put(None) + + def get_valgrind_suppression_file(self, subdir, name): + p = get_data_file(subdir, name) + if p: + return p + + self.error("Could not find any %s file" % name) + + def get_valgrind_suppressions(self): + return [self.get_valgrind_suppression_file('data', 'gstvalidate.supp')] + + def use_gdb(self, command): + if self.hard_timeout is not None: + self.hard_timeout *= GDB_TIMEOUT_FACTOR + self.timeout *= GDB_TIMEOUT_FACTOR + + if not self.options.gdb_non_stop: + self.timeout = sys.maxsize + self.hard_timeout = sys.maxsize + + args = ["gdb"] + if self.options.gdb_non_stop: + args += ["-ex", "run", "-ex", "backtrace", "-ex", "quit"] + args += ["--args"] + command + return args + + def use_rr(self, command, subenv): + command = ["rr", 'record', '-h'] + command + + self.timeout *= RR_TIMEOUT_FACTOR + self.rr_logdir = os.path.join(self.options.logsdir, self.classname.replace(".", os.sep), 'rr-logs') + subenv['_RR_TRACE_DIR'] = self.rr_logdir + try: + shutil.rmtree(self.rr_logdir, ignore_errors=False, onerror=None) + except FileNotFoundError: + pass + self.add_env_variable('_RR_TRACE_DIR', self.rr_logdir) + + return command + + def use_valgrind(self, command, subenv): + vglogsfile = os.path.splitext(self.logfile)[0] + '.valgrind' + self.extra_logfiles.add(vglogsfile) + + vg_args = [] + + for o, v in [('trace-children', 'yes'), + ('tool', 'memcheck'), + ('leak-check', 'full'), + ('leak-resolution', 'high'), + # TODO: errors-for-leak-kinds should be set to all instead of definite + # and all false positives should be added to suppression + # files. + ('errors-for-leak-kinds', 'definite,indirect'), + ('show-leak-kinds', 'definite,indirect'), + ('show-possibly-lost', 'no'), + ('num-callers', '20'), + ('error-exitcode', str(VALGRIND_ERROR_CODE)), + ('gen-suppressions', 'all')]: + vg_args.append("--%s=%s" % (o, v)) + + if not self.options.redirect_logs: + vglogsfile = os.path.splitext(self.logfile)[0] + '.valgrind' + self.extra_logfiles.add(vglogsfile) + vg_args.append("--%s=%s" % ('log-file', vglogsfile)) + + for supp in self.get_valgrind_suppressions(): + vg_args.append("--suppressions=%s" % supp) + + command = ["valgrind"] + vg_args + command + + # Tune GLib's memory allocator to be more valgrind friendly + subenv['G_DEBUG'] = 'gc-friendly' + subenv['G_SLICE'] = 'always-malloc' + + if self.hard_timeout is not None: + self.hard_timeout *= VALGRIND_TIMEOUT_FACTOR + self.timeout *= VALGRIND_TIMEOUT_FACTOR + + # Enable 'valgrind.config' + self.add_validate_config(get_data_file( + 'data', 'valgrind.config'), subenv) + if subenv == self.proc_env: + self.add_env_variable('G_DEBUG', 'gc-friendly') + self.add_env_variable('G_SLICE', 'always-malloc') + self.add_env_variable('GST_VALIDATE_CONFIG', + self.proc_env['GST_VALIDATE_CONFIG']) + + return command + + def add_validate_config(self, config, subenv=None): + if not subenv: + subenv = self.extra_env_variables + + cconf = subenv.get('GST_VALIDATE_CONFIG', "") + paths = [c for c in cconf.split(os.pathsep) if c] + [config] + subenv['GST_VALIDATE_CONFIG'] = os.pathsep.join(paths) + + def launch_server(self): + return None + + def get_logfile_repr(self): + if not self.options.redirect_logs: + if self.html_log: + log = self.html_log + else: + log = self.logfile + + if CI_ARTIFACTS_URL: + log = CI_ARTIFACTS_URL + os.path.relpath(log, self.options.logsdir) + + return "\n Log: %s" % (log) + + return "" + + def get_command_repr(self): + message = "%s %s" % (self._env_variable, ' '.join( + shlex.quote(arg) for arg in self.command)) + if self.server_command: + message = "%s & %s" % (self.server_command, message) + + return message + + def test_start(self, queue): + self.open_logfile() + + self.server_command = self.launch_server() + self.queue = queue + self.command = [self.application] + self._starting_time = time.time() + self.build_arguments() + self.proc_env = self.get_subproc_env() + + for var, value in list(self.extra_env_variables.items()): + value = self.proc_env.get(var, '') + os.pathsep + value + self.proc_env[var] = value.strip(os.pathsep) + self.add_env_variable(var, self.proc_env[var]) + + if self.options.gdb: + self.command = self.use_gdb(self.command) + + self.previous_sigint_handler = signal.getsignal(signal.SIGINT) + # Make the gst-validate executable ignore SIGINT while gdb is + # running. + signal.signal(signal.SIGINT, signal.SIG_IGN) + + if self.options.valgrind: + self.command = self.use_valgrind(self.command, self.proc_env) + + if self.options.rr: + self.command = self.use_rr(self.command, self.proc_env) + + if not self.options.redirect_logs: + self.out.write("# `%s`\n\n" + "## Command\n\n``` bash\n%s\n```\n\n" % ( + self.classname, self.get_command_repr())) + self.out.write("## %s output\n\n``` \n\n" % os.path.basename(self.application)) + self.out.flush() + else: + message = "Launching: %s%s\n" \ + " Command: %s\n" % (Colors.ENDC, self.classname, + self.get_command_repr()) + printc(message, Colors.OKBLUE) + + self.thread = threading.Thread(target=self.thread_wrapper) + self.thread.start() + + self.last_val = 0 + self.last_change_ts = time.time() + self.start_ts = time.time() + + def _dump_log_file(self, logfile): + if which('bat'): + try: + subprocess.check_call(['bat', '-H', '1', '--paging=never', logfile]) + return + except (subprocess.CalledProcessError, FileNotFoundError): + pass + + with open(logfile, 'r') as fin: + for line in fin.readlines(): + print('> ' + line, end='') + + def _dump_log_files(self): + self._dump_log_file(self.logfile) + + def copy_logfiles(self, extra_folder="flaky_tests"): + path = os.path.dirname(os.path.join(self.options.logsdir, extra_folder, + self.classname.replace(".", os.sep))) + mkdir(path) + self.logfile = shutil.copy(self.logfile, path) + extra_logs = [] + for logfile in self.extra_logfiles: + extra_logs.append(shutil.copy(logfile, path)) + self.extra_logfiles = extra_logs + + def test_end(self, retry_on_failure=False): + self.kill_subprocess() + self.thread.join() + self.time_taken = time.time() - self._starting_time + + if self.options.gdb: + signal.signal(signal.SIGINT, self.previous_sigint_handler) + + self.finalize_logfiles() + message = None + end = "\n" + + if self.options.dump_on_failure: + if self.result not in [Result.PASSED, Result.KNOWN_ERROR, Result.NOT_RUN]: + self._dump_log_files() + + # Only keep around env variables we need later + clean_env = {} + for n in self.__env_variable: + clean_env[n] = self.proc_env.get(n, None) + self.proc_env = clean_env + + # Don't keep around JSON report objects, they were processed + # in check_results already + self.reports = [] + + return self.result + + +class GstValidateTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): + pass + + +class GstValidateListener(socketserver.BaseRequestHandler, Loggable): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + Loggable.__init__(self, "GstValidateListener") + + def handle(self): + """Implements BaseRequestHandler handle method""" + test = None + self.logCategory = "GstValidateListener" + while True: + raw_len = self.request.recv(4) + if raw_len == b'': + return + msglen = struct.unpack('>I', raw_len)[0] + e = None + raw_msg = bytes() + while msglen != len(raw_msg): + raw_msg += self.request.recv(msglen - len(raw_msg)) + if e is not None: + continue + try: + msg = raw_msg.decode('utf-8', 'ignore') + except UnicodeDecodeError as e: + self.error("%s Could not decode message: %s - %s" % (test.classname if test else "unknown", msg, e)) + continue + + if msg == '': + return + + try: + obj = json.loads(msg) + except json.decoder.JSONDecodeError as e: + self.error("%s Could not decode message: %s - %s" % (test.classname if test else "unknown", msg, e)) + continue + + if test is None: + # First message must contain the uuid + uuid = obj.get("uuid", None) + if uuid is None: + return + # Find test from launcher + for t in self.server.launcher.tests: + if uuid == t.get_uuid(): + test = t + break + if test is None: + self.server.launcher.error( + "Could not find test for UUID %s" % uuid) + return + + obj_type = obj.get("type", '') + if obj_type == 'position': + test.set_position(obj['position'], obj['duration'], + obj['speed']) + elif obj_type == 'buffering': + test.set_position(obj['position'], 100) + elif obj_type == 'action': + test.add_action_execution(obj) + # Make sure that action is taken into account when checking if process + # is updating + test.position += 1 + elif obj_type == 'action-done': + # Make sure that action end is taken into account when checking if process + # is updating + test.position += 1 + test.actions_infos[-1]['execution-duration'] = obj['execution-duration'] + elif obj_type == 'report': + test.add_report(obj) + elif obj_type == 'skip-test': + test.set_result(Result.SKIPPED) + + +class GstValidateTest(Test): + + """ A class representing a particular test. """ + HARD_TIMEOUT_FACTOR = 5 + fault_sig_regex = re.compile("") + needs_gst_inspect = set() + + def __init__(self, application_name, classname, + options, reporter, duration=0, + timeout=DEFAULT_TIMEOUT, scenario=None, hard_timeout=None, + media_descriptor=None, extra_env_variables=None, + expected_issues=None, workdir=None): + + extra_env_variables = extra_env_variables or {} + + if not hard_timeout and self.HARD_TIMEOUT_FACTOR: + if timeout: + hard_timeout = timeout * self.HARD_TIMEOUT_FACTOR + elif duration: + hard_timeout = duration * self.HARD_TIMEOUT_FACTOR + else: + hard_timeout = None + + # If we are running from source, use the -debug version of the + # application which is using rpath instead of libtool's wrappers. It's + # slightly faster to start and will not confuse valgrind. + debug = '%s-debug' % application_name + p = look_for_file_in_source_dir('tools', debug) + if p: + application_name = p + + self.reports = [] + self.position = -1 + self.media_duration = -1 + self.speed = 1.0 + self.actions_infos = [] + self.media_descriptor = media_descriptor + self.server = None + self.criticals = [] + + override_path = self.get_override_file(media_descriptor) + if override_path: + if extra_env_variables: + if extra_env_variables.get("GST_VALIDATE_OVERRIDE", ""): + extra_env_variables[ + "GST_VALIDATE_OVERRIDE"] += os.path.pathsep + + extra_env_variables["GST_VALIDATE_OVERRIDE"] = override_path + + super(GstValidateTest, self).__init__(application_name, classname, + options, reporter, + duration=duration, + timeout=timeout, + hard_timeout=hard_timeout, + extra_env_variables=extra_env_variables, + expected_issues=expected_issues, + workdir=workdir) + if media_descriptor and media_descriptor.get_media_filepath(): + config_file = os.path.join(media_descriptor.get_media_filepath() + '.config') + if os.path.isfile(config_file): + self.add_validate_config(config_file, extra_env_variables) + + if scenario is None or scenario.name.lower() == "none": + self.scenario = None + else: + self.scenario = scenario + + def kill_subprocess(self): + Test.kill_subprocess(self) + + def add_report(self, report): + self.reports.append(report) + + def set_position(self, position, duration, speed=None): + self.position = position + self.media_duration = duration + if speed: + self.speed = speed + + def add_action_execution(self, action_infos): + self.actions_infos.append(action_infos) + + def get_override_file(self, media_descriptor): + if media_descriptor: + if media_descriptor.get_path(): + override_path = os.path.splitext(media_descriptor.get_path())[ + 0] + VALIDATE_OVERRIDE_EXTENSION + if os.path.exists(override_path): + return override_path + + return None + + def get_current_position(self): + return self.position + + def get_current_value(self): + return self.position + + def get_subproc_env(self): + subproc_env = os.environ.copy() + + if self.options.validate_default_config: + self.add_validate_config(self.options.validate_default_config, + subproc_env, ) + + subproc_env["GST_VALIDATE_UUID"] = self.get_uuid() + subproc_env["GST_VALIDATE_LOGSDIR"] = self.options.logsdir + + if 'GST_DEBUG' in os.environ and not self.options.redirect_logs: + gstlogsfile = os.path.splitext(self.logfile)[0] + '.gstdebug' + self.extra_logfiles.add(gstlogsfile) + subproc_env["GST_DEBUG_FILE"] = gstlogsfile + + if self.options.no_color: + subproc_env["GST_DEBUG_NO_COLOR"] = '1' + + # Ensure XInitThreads is called, see bgo#731525 + subproc_env['GST_GL_XINITTHREADS'] = '1' + self.add_env_variable('GST_GL_XINITTHREADS', '1') + + if self.scenario is not None: + scenario = self.scenario.get_execution_name() + subproc_env["GST_VALIDATE_SCENARIO"] = scenario + self.add_env_variable("GST_VALIDATE_SCENARIO", + subproc_env["GST_VALIDATE_SCENARIO"]) + else: + try: + del subproc_env["GST_VALIDATE_SCENARIO"] + except KeyError: + pass + + if not subproc_env.get('GST_DEBUG_DUMP_DOT_DIR'): + dotfilesdir = os.path.join(self.options.logsdir, + self.classname.replace(".", os.sep) + '.pipelines_dot_files') + mkdir(dotfilesdir) + subproc_env['GST_DEBUG_DUMP_DOT_DIR'] = dotfilesdir + if CI_ARTIFACTS_URL: + dotfilesurl = CI_ARTIFACTS_URL + os.path.relpath(dotfilesdir, + self.options.logsdir) + subproc_env['GST_VALIDATE_DEBUG_DUMP_DOT_URL'] = dotfilesurl + + return subproc_env + + def clean(self): + Test.clean(self) + self.reports = [] + self.position = -1 + self.media_duration = -1 + self.speed = 1.0 + self.actions_infos = [] + + def build_arguments(self): + super(GstValidateTest, self).build_arguments() + if "GST_VALIDATE" in os.environ: + self.add_env_variable("GST_VALIDATE", os.environ["GST_VALIDATE"]) + + if "GST_VALIDATE_SCENARIOS_PATH" in os.environ: + self.add_env_variable("GST_VALIDATE_SCENARIOS_PATH", + os.environ["GST_VALIDATE_SCENARIOS_PATH"]) + + self.add_env_variable("GST_VALIDATE_CONFIG") + self.add_env_variable("GST_VALIDATE_OVERRIDE") + + def get_extra_log_content(self, extralog): + value = Test.get_extra_log_content(self, extralog) + + return value + + def report_matches_expected_issues(self, report, expected_issue): + for key in ['bug', 'bugs', 'sometimes']: + if key in expected_issue: + del expected_issue[key] + for key, value in list(report.items()): + if key in expected_issue: + if not re.findall(expected_issue[key], str(value)): + return False + expected_issue.pop(key) + + if "can-happen-several-times" in expected_issue: + expected_issue.pop("can-happen-several-times") + return not bool(expected_issue) + + def check_reported_issues(self, expected_issues): + ret = [] + expected_retcode = [0] + for report in self.reports: + found = None + for expected_issue in expected_issues: + if self.report_matches_expected_issues(report, + expected_issue.copy()): + found = expected_issue + break + + if found is not None: + if not found.get('can-happen-several-times', False): + expected_issues.remove(found) + if report['level'] == 'critical': + if found.get('sometimes', True) and isinstance(expected_retcode, list): + expected_retcode.append(18) + else: + expected_retcode = [18] + elif report['level'] == 'critical': + ret.append(report) + + if not ret: + return None, expected_issues, expected_retcode + + return ret, expected_issues, expected_retcode + + def check_expected_issue(self, expected_issue): + res = True + msg = '' + expected_symbols = expected_issue.get('stacktrace_symbols') + if expected_symbols: + trace_gatherer = BackTraceGenerator.get_default() + stack_trace = trace_gatherer.get_trace(self) + + if stack_trace: + if not isinstance(expected_symbols, list): + expected_symbols = [expected_symbols] + + not_found_symbols = [s for s in expected_symbols + if s not in stack_trace] + if not_found_symbols: + msg = " Expected symbols '%s' not found in stack trace " % ( + not_found_symbols) + res = False + else: + msg += " No stack trace available, could not verify symbols " + + _, not_found_expected_issues, _ = self.check_reported_issues(expected_issue.get('issues', [])) + if not_found_expected_issues: + mandatory_failures = [f for f in not_found_expected_issues + if not f.get('sometimes', True)] + if mandatory_failures: + msg = " (Expected issues not found: %s) " % mandatory_failures + res = False + + return msg, res + + def check_expected_timeout(self, expected_timeout): + msg = "Expected timeout happened. " + result = Result.PASSED + message = expected_timeout.get('message') + if message: + if not re.findall(message, self.message): + result = Result.FAILED + msg = "Expected timeout message: %s got %s " % ( + message, self.message) + + stack_msg, stack_res = self.check_expected_issue(expected_timeout) + if not stack_res: + result = Result.TIMEOUT + msg += stack_msg + + return result, msg + + def check_results(self): + if self.result in [Result.FAILED, Result.PASSED, Result.SKIPPED]: + return + + self.debug("%s returncode: %s", self, self.process.returncode) + expected_issues = copy.deepcopy(self.expected_issues) + if self.options.rr: + # signal.SIGPPIPE is 13 but it sometimes isn't present in python for some reason. + expected_issues.append({"returncode": -13, "sometimes": True}) + self.criticals, not_found_expected_issues, expected_returncode = self.check_reported_issues(expected_issues) + expected_timeout = None + expected_signal = None + for i, f in enumerate(not_found_expected_issues): + returncode = f.get('returncode', []) + if not isinstance(returncode, list): + returncode = [returncode] + + if f.get('signame'): + signames = f['signame'] + if not isinstance(signames, list): + signames = [signames] + + returncode = [EXITING_SIGNALS[signame] for signame in signames] + + if returncode: + if 'sometimes' in f: + returncode.append(0) + expected_returncode = returncode + expected_signal = f + elif f.get("timeout"): + expected_timeout = f + + not_found_expected_issues = [f for f in not_found_expected_issues + if not f.get('returncode') and not f.get('signame')] + + msg = "" + result = Result.PASSED + if self.result == Result.TIMEOUT: + with open(self.logfile) as f: + signal_fault_info = self.fault_sig_regex.findall(f.read()) + if signal_fault_info: + result = Result.FAILED + msg = signal_fault_info[0] + elif expected_timeout: + not_found_expected_issues.remove(expected_timeout) + result, msg = self.check_expected_timeout(expected_timeout) + else: + return + elif self.process.returncode in EXITING_SIGNALS: + msg = "Application exited with signal %s" % ( + EXITING_SIGNALS[self.process.returncode]) + if self.process.returncode not in expected_returncode: + result = Result.FAILED + else: + if expected_signal: + stack_msg, stack_res = self.check_expected_issue( + expected_signal) + if not stack_res: + msg += stack_msg + result = Result.FAILED + self.add_stack_trace_to_logfile() + elif self.process.returncode == VALGRIND_ERROR_CODE: + msg = "Valgrind reported errors " + result = Result.FAILED + elif self.process.returncode not in expected_returncode: + msg = "Application returned %s " % self.process.returncode + if expected_returncode != [0]: + msg += "(expected %s) " % expected_returncode + result = Result.FAILED + + if self.criticals: + msg += "(critical errors: [%s]) " % ', '.join(set([c['summary'] + for c in self.criticals])) + result = Result.FAILED + + if not_found_expected_issues: + mandatory_failures = [f for f in not_found_expected_issues + if not f.get('sometimes', True)] + + if mandatory_failures: + msg += " (Expected errors not found: %s) " % mandatory_failures + result = Result.FAILED + elif self.expected_issues: + msg += ' %s(Expected errors occurred: %s)%s' % (Colors.OKBLUE, + self.expected_issues, + Colors.ENDC) + result = Result.KNOWN_ERROR + + if result == Result.PASSED: + for report in self.reports: + if report["level"] == "expected": + result = Result.KNOWN_ERROR + break + + self.set_result(result, msg.strip()) + + def _generate_expected_issues(self): + res = "" + self.criticals = self.criticals or [] + if self.result == Result.TIMEOUT: + res += """ { + 'timeout': True, + 'sometimes': True, + },""" + + for report in self.criticals: + res += "\n%s{" % (" " * 12) + + for key, value in report.items(): + if key == "type": + continue + if value is None: + continue + res += '\n%s%s"%s": "%s",' % ( + " " * 16, "# " if key == "details" else "", + key, value.replace('\n', '\\n')) + + res += "\n%s}," % (" " * 12) + + return res + + def get_valgrind_suppressions(self): + result = super(GstValidateTest, self).get_valgrind_suppressions() + result.extend(utils.get_gst_build_valgrind_suppressions()) + gst_sup = self.get_valgrind_suppression_file('common', 'gst.supp') + if gst_sup: + result.append(gst_sup) + + return result + + +class GstValidateEncodingTestInterface(object): + DURATION_TOLERANCE = GST_SECOND / 4 + + def __init__(self, combination, media_descriptor, duration_tolerance=None): + super(GstValidateEncodingTestInterface, self).__init__() + + self.media_descriptor = media_descriptor + self.combination = combination + self.dest_file = "" + + self._duration_tolerance = duration_tolerance + if duration_tolerance is None: + self._duration_tolerance = self.DURATION_TOLERANCE + + def get_current_size(self): + try: + size = os.stat(urllib.parse.urlparse(self.dest_file).path).st_size + except OSError: + return None + + self.debug("Size: %s" % size) + return size + + def _get_profile_full(self, muxer, venc, aenc, video_restriction=None, + audio_restriction=None, audio_presence=0, + video_presence=0, variable_framerate=False): + ret = "" + if muxer: + ret += muxer + ret += ":" + if venc: + if video_restriction is not None: + ret = ret + video_restriction + '->' + ret += venc + props = "" + if video_presence: + props += 'presence=%s,' % str(video_presence) + if variable_framerate: + props += 'variable-framerate=true,' + if props: + ret = ret + '|' + props[:-1] + if aenc: + ret += ":" + if audio_restriction is not None: + ret = ret + audio_restriction + '->' + ret += aenc + if audio_presence: + ret = ret + '|' + str(audio_presence) + + return ret.replace("::", ":") + + def get_profile(self, video_restriction=None, audio_restriction=None, + variable_framerate=False): + vcaps = self.combination.get_video_caps() + acaps = self.combination.get_audio_caps() + if video_restriction is None: + video_restriction = self.combination.video_restriction + if audio_restriction is None: + audio_restriction = self.combination.audio_restriction + if self.media_descriptor is not None: + if self.combination.video == "theora": + # Theoraenc doesn't support variable framerate, make sure to avoid them + framerate = self.media_descriptor.get_framerate() + if framerate == Fraction(0, 1): + framerate = Fraction(30, 1) + restriction = utils.GstCaps.new_from_str(video_restriction or "video/x-raw") + for struct, _ in restriction: + if struct.get("framerate") is None: + struct.set("framerate", struct.FRACTION_TYPE, framerate) + video_restriction = str(restriction) + + video_presence = self.media_descriptor.get_num_tracks("video") + if video_presence == 0: + vcaps = None + + audio_presence = self.media_descriptor.get_num_tracks("audio") + if audio_presence == 0: + acaps = None + + return self._get_profile_full(self.combination.get_muxer_caps(), + vcaps, acaps, + audio_presence=audio_presence, + video_presence=video_presence, + video_restriction=video_restriction, + audio_restriction=audio_restriction, + variable_framerate=variable_framerate) + + def _clean_caps(self, caps): + """ + Returns a list of key=value or structure name, without "(types)" or ";" or "," + """ + return re.sub(r"\(.+?\)\s*| |;", '', caps).split(',') + + # pylint: disable=E1101 + def _has_caps_type_variant(self, c, ccaps): + """ + Handle situations where we can have application/ogg or video/ogg or + audio/ogg + """ + has_variant = False + media_type = re.findall("application/|video/|audio/", c) + if media_type: + media_type = media_type[0].replace('/', '') + possible_mtypes = ["application", "video", "audio"] + possible_mtypes.remove(media_type) + for tmptype in possible_mtypes: + possible_c_variant = c.replace(media_type, tmptype) + if possible_c_variant in ccaps: + self.info( + "Found %s in %s, good enough!", possible_c_variant, ccaps) + has_variant = True + + return has_variant + + # pylint: disable=E1101 + def run_iqa_test(self, reference_file_uri): + """ + Runs IQA test if @reference_file_path exists + @test: The test to run tests on + """ + if not GstValidateBaseTestManager.has_feature('iqa'): + self.debug('Iqa element not present, not running extra test.') + return + + pipeline_desc = """ + uridecodebin uri=%s ! + iqa name=iqa do-dssim=true dssim-error-threshold=1.0 ! fakesink + uridecodebin uri=%s ! iqa. + """ % (reference_file_uri, self.dest_file) + pipeline_desc = pipeline_desc.replace("\n", "") + + command = [GstValidateBaseTestManager.COMMAND] + \ + shlex.split(pipeline_desc) + msg = "## Running IQA tests on results of: " \ + + "%s\n### Command: \n```\n%s\n```\n" % ( + self.classname, ' '.join(command)) + if not self.options.redirect_logs: + self.out.write(msg) + self.out.flush() + else: + printc(msg, Colors.OKBLUE) + + self.process = subprocess.Popen(command, + stderr=self.out, + stdout=self.out, + env=self.proc_env, + cwd=self.workdir) + self.process.wait() + + def check_encoded_file(self): + result_descriptor = GstValidateMediaDescriptor.new_from_uri( + self.dest_file) + if result_descriptor is None: + return (Result.FAILED, "Could not discover encoded file %s" + % self.dest_file) + + duration = result_descriptor.get_duration() + orig_duration = self.media_descriptor.get_duration() + tolerance = self._duration_tolerance + + if orig_duration - tolerance >= duration <= orig_duration + tolerance: + os.remove(result_descriptor.get_path()) + self.add_report( + { + 'type': 'report', + 'issue-id': 'transcoded-file-wrong-duration', + 'summary': 'The duration of a transcoded file doesn\'t match the duration of the original file', + 'level': 'critical', + 'detected-on': 'pipeline', + 'details': "Duration of encoded file is " " wrong (%s instead of %s)" % ( + utils.TIME_ARGS(duration), utils.TIME_ARGS(orig_duration)) + } + ) + else: + all_tracks_caps = result_descriptor.get_tracks_caps() + container_caps = result_descriptor.get_caps() + if container_caps: + all_tracks_caps.insert(0, ("container", container_caps)) + + for track_type, caps in all_tracks_caps: + ccaps = self._clean_caps(caps) + wanted_caps = self.combination.get_caps(track_type) + cwanted_caps = self._clean_caps(wanted_caps) + + if wanted_caps is None: + os.remove(result_descriptor.get_path()) + self.add_report( + { + 'type': 'report', + 'issue-id': 'transcoded-file-wrong-stream-type', + 'summary': 'Expected stream types during transcoding do not match expectations', + 'level': 'critical', + 'detected-on': 'pipeline', + 'details': "Found a track of type %s in the encoded files" + " but none where wanted in the encoded profile: %s" % ( + track_type, self.combination) + } + ) + return + + for c in cwanted_caps: + if c not in ccaps: + if not self._has_caps_type_variant(c, ccaps): + os.remove(result_descriptor.get_path()) + self.add_report( + { + 'type': 'report', + 'issue-id': 'transcoded-file-wrong-caps', + 'summary': 'Expected stream caps during transcoding do not match expectations', + 'level': 'critical', + 'detected-on': 'pipeline', + 'details': "Field: %s (from %s) not in caps of the outputted file %s" % ( + wanted_caps, c, ccaps) + } + ) + return + + os.remove(result_descriptor.get_path()) + + +class TestsManager(Loggable): + + """ A class responsible for managing tests. """ + + name = "base" + loading_testsuite = None + + def __init__(self): + + Loggable.__init__(self) + + self.tests = [] + self.unwanted_tests = [] + self.options = None + self.args = None + self.reporter = None + self.wanted_tests_patterns = [] + self.blacklisted_tests_patterns = [] + self._generators = [] + self.check_testslist = True + self.all_tests = None + self.expected_issues = {} + self.blacklisted_tests = [] + + def init(self): + return True + + def list_tests(self): + return sorted(list(self.tests), key=lambda x: x.classname) + + def find_tests(self, classname): + regex = re.compile(classname) + return [test for test in self.list_tests() if regex.findall(test.classname)] + + def add_expected_issues(self, expected_issues): + for bugid, failure_def in list(expected_issues.items()): + tests_regexes = [] + for test_name_regex in failure_def['tests']: + regex = re.compile(test_name_regex) + tests_regexes.append(regex) + for test in self.tests: + if regex.findall(test.classname): + if failure_def.get('allow_flakiness'): + test.allow_flakiness = True + self.debug("%s allow flakiness" % (test.classname)) + else: + for issue in failure_def['issues']: + issue['bug'] = bugid + test.expected_issues.extend(failure_def['issues']) + self.debug("%s added expected issues from %s" % ( + test.classname, bugid)) + failure_def['tests'] = tests_regexes + + self.expected_issues.update(expected_issues) + + def add_test(self, test): + if test.generator is None: + test.classname = self.loading_testsuite + '.' + test.classname + + for bugid, failure_def in list(self.expected_issues.items()): + failure_def['bug'] = bugid + for regex in failure_def['tests']: + if regex.findall(test.classname): + if failure_def.get('allow_flakiness'): + test.allow_flakiness = True + self.debug("%s allow flakiness" % (test.classname)) + else: + for issue in failure_def['issues']: + issue['bug'] = bugid + test.expected_issues.extend(failure_def['issues']) + self.debug("%s added expected issues from %s" % ( + test.classname, bugid)) + + if self._is_test_wanted(test): + if test not in self.tests: + self.tests.append(test) + else: + if test not in self.tests: + self.unwanted_tests.append(test) + + def get_tests(self): + return self.tests + + def populate_testsuite(self): + pass + + def add_generators(self, generators): + """ + @generators: A list of, or one single #TestsGenerator to be used to generate tests + """ + if not isinstance(generators, list): + generators = [generators] + self._generators.extend(generators) + for generator in generators: + generator.testsuite = self.loading_testsuite + + self._generators = list(set(self._generators)) + + def get_generators(self): + return self._generators + + def _add_blacklist(self, blacklisted_tests): + if not isinstance(blacklisted_tests, list): + blacklisted_tests = [blacklisted_tests] + + for patterns in blacklisted_tests: + for pattern in patterns.split(","): + self.blacklisted_tests_patterns.append(re.compile(pattern)) + + def set_default_blacklist(self, default_blacklist): + for test_regex, reason in default_blacklist: + if not test_regex.startswith(self.loading_testsuite + '.'): + test_regex = self.loading_testsuite + '.' + test_regex + self.blacklisted_tests.append((test_regex, reason)) + self._add_blacklist(test_regex) + + def add_options(self, parser): + """ Add more arguments. """ + pass + + def set_settings(self, options, args, reporter): + """ Set properties after options parsing. """ + self.options = options + self.args = args + self.reporter = reporter + + self.populate_testsuite() + + if self.options.valgrind: + self.print_valgrind_bugs() + + if options.wanted_tests: + for patterns in options.wanted_tests: + for pattern in patterns.split(","): + self.wanted_tests_patterns.append(re.compile(pattern)) + + if options.blacklisted_tests: + for patterns in options.blacklisted_tests: + self._add_blacklist(patterns) + + def check_blacklists(self): + if self.options.check_bugs_status: + if not check_bugs_resolution(self.blacklisted_tests): + return False + + return True + + def log_blacklists(self): + if self.blacklisted_tests: + self.info("Currently 'hardcoded' %s blacklisted tests:" % + self.name) + + for name, bug in self.blacklisted_tests: + if not self.options.check_bugs_status: + self.info(" + %s --> bug: %s" % (name, bug)) + + def check_expected_issues(self): + if not self.expected_issues or not self.options.check_bugs_status: + return True + + bugs_definitions = defaultdict(list) + for bug, failure_def in list(self.expected_issues.items()): + tests_names = '|'.join( + [regex.pattern for regex in failure_def['tests']]) + bugs_definitions[tests_names].extend([bug]) + + return check_bugs_resolution(bugs_definitions.items()) + + def _check_blacklisted(self, test): + for pattern in self.blacklisted_tests_patterns: + if pattern.findall(test.classname): + self.info("%s is blacklisted by %s", test.classname, pattern) + return True + + return False + + def _check_whitelisted(self, test): + for pattern in self.wanted_tests_patterns: + if pattern.findall(test.classname): + if self._check_blacklisted(test): + # If explicitly white listed that specific test + # bypass the blacklisting + if pattern.pattern != test.classname: + return False + return True + return False + + def _check_duration(self, test): + if test.duration > 0 and int(self.options.long_limit) < int(test.duration): + self.info("Not activating %s as its duration (%d) is superior" + " than the long limit (%d)" % (test, test.duration, + int(self.options.long_limit))) + return False + + return True + + def _is_test_wanted(self, test): + if self._check_whitelisted(test): + if not self._check_duration(test): + return False + return True + + if self._check_blacklisted(test): + return False + + if not self._check_duration(test): + return False + + if not self.wanted_tests_patterns: + return True + + return False + + def needs_http_server(self): + return False + + def print_valgrind_bugs(self): + pass + + +class TestsGenerator(Loggable): + + def __init__(self, name, test_manager, tests=[]): + Loggable.__init__(self) + self.name = name + self.test_manager = test_manager + self.testsuite = None + self._tests = {} + for test in tests: + self._tests[test.classname] = test + + def generate_tests(self, *kwargs): + """ + Method that generates tests + """ + return list(self._tests.values()) + + def add_test(self, test): + test.generator = self + test.classname = self.testsuite + '.' + test.classname + self._tests[test.classname] = test + + +class GstValidateTestsGenerator(TestsGenerator): + + def populate_tests(self, uri_minfo_special_scenarios, scenarios): + pass + + def generate_tests(self, uri_minfo_special_scenarios, scenarios): + self.populate_tests(uri_minfo_special_scenarios, scenarios) + return super(GstValidateTestsGenerator, self).generate_tests() + + +class _TestsLauncher(Loggable): + + def __init__(self): + + Loggable.__init__(self) + + self.options = None + self.testers = [] + self.tests = [] + self.reporter = None + self._list_testers() + self.all_tests = None + self.wanted_tests_patterns = [] + + self.queue = queue.Queue() + self.jobs = [] + self.total_num_tests = 0 + self.current_progress = -1 + self.server = None + self.httpsrv = None + self.vfb_server = None + + def _list_app_dirs(self): + app_dirs = [] + env_dirs = os.environ["GST_VALIDATE_APPS_DIR"] + if env_dirs is not None: + for dir_ in env_dirs.split(os.pathsep): + app_dirs.append(dir_) + + return app_dirs + + def _exec_app(self, app_dir, env): + try: + files = os.listdir(app_dir) + except OSError as e: + self.debug("Could not list %s: %s" % (app_dir, e)) + files = [] + for f in files: + if f.endswith(".py"): + exec(compile(open(os.path.join(app_dir, f)).read(), + os.path.join(app_dir, f), 'exec'), env) + + def _exec_apps(self, env): + app_dirs = self._list_app_dirs() + for app_dir in app_dirs: + self._exec_app(app_dir, env) + + def _list_testers(self): + env = globals().copy() + self._exec_apps(env) + + testers = [i() for i in utils.get_subclasses(TestsManager, env)] + for tester in testers: + if tester.init() is True: + self.testers.append(tester) + else: + self.warning("Can not init tester: %s -- PATH is %s" + % (tester.name, os.environ["PATH"])) + + def add_options(self, parser): + for tester in self.testers: + tester.add_options(parser) + + def _load_testsuite(self, testsuites): + exceptions = [] + for testsuite in testsuites: + try: + sys.path.insert(0, os.path.dirname(testsuite)) + spec = importlib.util.spec_from_file_location(os.path.basename(testsuite).replace(".py", ""), testsuite) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return (module, None) + except Exception as e: + exceptions.append("Could not load %s: %s" % (testsuite, e)) + continue + finally: + sys.path.remove(os.path.dirname(testsuite)) + + return (None, exceptions) + + def _load_testsuites(self): + testsuites = {} + for testsuite in self.options.testsuites: + if testsuite.endswith('.py') and os.path.exists(testsuite): + testsuite = os.path.abspath(os.path.expanduser(testsuite)) + loaded_module = self._load_testsuite([testsuite]) + else: + possible_testsuites_paths = [os.path.join(d, testsuite + ".py") + for d in self.options.testsuites_dirs] + loaded_module = self._load_testsuite(possible_testsuites_paths) + + module = loaded_module[0] + if not loaded_module[0]: + if "." in testsuite: + self.options.testsuites.append(testsuite.split('.')[0]) + self.info("%s looks like a test name, trying that" % + testsuite) + self.options.wanted_tests.append(testsuite) + else: + if testsuite in testsuites: + self.info('Testuite %s was loaded previously', testsuite) + continue + printc("Could not load testsuite: %s, reasons: %s" % ( + testsuite, loaded_module[1]), Colors.FAIL) + continue + + if module.__name__ in testsuites: + self.info("Trying to load testsuite '%s' a second time?", module.__name__) + continue + + testsuites[module.__name__] = module + if not hasattr(module, "TEST_MANAGER"): + module.TEST_MANAGER = [tester.name for tester in self.testers] + elif not isinstance(module.TEST_MANAGER, list): + module.TEST_MANAGER = [module.TEST_MANAGER] + + self.options.testsuites = list(testsuites.values()) + + def _setup_testsuites(self): + for testsuite in self.options.testsuites: + loaded = False + wanted_test_manager = None + # TEST_MANAGER has been set in _load_testsuites() + assert hasattr(testsuite, "TEST_MANAGER") + wanted_test_manager = testsuite.TEST_MANAGER + if not isinstance(wanted_test_manager, list): + wanted_test_manager = [wanted_test_manager] + + for tester in self.testers: + if wanted_test_manager is not None and \ + tester.name not in wanted_test_manager: + continue + + prev_testsuite_name = TestsManager.loading_testsuite + if self.options.user_paths: + TestsManager.loading_testsuite = tester.name + tester.register_defaults() + loaded = True + else: + TestsManager.loading_testsuite = testsuite.__name__ + if testsuite.setup_tests(tester, self.options): + loaded = True + if prev_testsuite_name: + TestsManager.loading_testsuite = prev_testsuite_name + + if not loaded: + printc("Could not load testsuite: %s" + " maybe because of missing TestManager" + % (testsuite), Colors.FAIL) + return False + + def _load_config(self, options): + printc("Loading config files is DEPRECATED" + " you should use the new testsuite format now",) + + for tester in self.testers: + tester.options = options + globals()[tester.name] = tester + globals()["options"] = options + c__file__ = __file__ + globals()["__file__"] = self.options.config + exec(compile(open(self.options.config).read(), + self.options.config, 'exec'), globals()) + globals()["__file__"] = c__file__ + + def set_settings(self, options, args): + if options.xunit_file: + self.reporter = reporters.XunitReporter(options) + else: + self.reporter = reporters.Reporter(options) + + self.options = options + wanted_testers = None + for tester in self.testers: + if tester.name in args: + wanted_testers = tester.name + + if wanted_testers: + testers = self.testers + self.testers = [] + for tester in testers: + if tester.name in args: + self.testers.append(tester) + args.remove(tester.name) + + if options.config: + self._load_config(options) + + self._load_testsuites() + if not self.options.testsuites: + printc("Not testsuite loaded!", Colors.FAIL) + return False + + for tester in self.testers: + tester.set_settings(options, args, self.reporter) + + if not options.config and options.testsuites: + if self._setup_testsuites() is False: + return False + + if self.options.check_bugs_status: + printc("-> Checking bugs resolution... ", end='') + + for tester in self.testers: + if not tester.check_blacklists(): + return False + + tester.log_blacklists() + + if not tester.check_expected_issues(): + return False + + if self.options.check_bugs_status: + printc("OK", Colors.OKGREEN) + + if self.needs_http_server() or options.httponly is True: + self.httpsrv = HTTPServer(options) + self.httpsrv.start() + + if options.no_display: + self.vfb_server = get_virual_frame_buffer_server(options) + res = self.vfb_server.start() + if res[0] is False: + printc("Could not start virtual frame server: %s" % res[1], + Colors.FAIL) + return False + os.environ["DISPLAY"] = self.vfb_server.display_id + + return True + + def _check_tester_has_other_testsuite(self, testsuite, tester): + if tester.name != testsuite.TEST_MANAGER[0]: + return True + + for t in self.options.testsuites: + if t != testsuite: + for other_testmanager in t.TEST_MANAGER: + if other_testmanager == tester.name: + return True + + return False + + def _check_defined_tests(self, tester, tests): + if self.options.blacklisted_tests or self.options.wanted_tests: + return + + tests_names = [test.classname for test in tests] + testlist_changed = False + for testsuite in self.options.testsuites: + if not self._check_tester_has_other_testsuite(testsuite, tester) \ + and tester.check_testslist: + try: + testlist_file = open(os.path.splitext(testsuite.__file__)[0] + ".testslist", + 'r+') + + know_tests = testlist_file.read().split("\n") + testlist_file.close() + + testlist_file = open(os.path.splitext(testsuite.__file__)[0] + ".testslist", + 'w') + except IOError: + continue + + optional_out = [] + for test in know_tests: + if test and test.strip('~') not in tests_names: + if not test.startswith('~'): + testlist_changed = True + printc("Test %s Not in testsuite %s anymore" + % (test, testsuite.__file__), Colors.FAIL) + else: + optional_out.append((test, None)) + + tests_names = sorted([(test.classname, test) for test in tests] + optional_out, + key=lambda x: x[0].strip('~')) + + for tname, test in tests_names: + if test and test.optional: + tname = '~' + tname + testlist_file.write("%s\n" % (tname)) + if tname and tname not in know_tests: + printc("Test %s is NEW in testsuite %s" + % (tname, testsuite.__file__), + Colors.FAIL if self.options.fail_on_testlist_change else Colors.OKGREEN) + testlist_changed = True + + testlist_file.close() + break + + return testlist_changed + + def _split_tests(self, num_groups): + groups = [[] for x in range(num_groups)] + group = cycle(groups) + for test in self.tests: + next(group).append(test) + return groups + + def list_tests(self): + for tester in self.testers: + if not self._tester_needed(tester): + continue + + tests = tester.list_tests() + if self._check_defined_tests(tester, tests) and \ + self.options.fail_on_testlist_change: + raise RuntimeError("Unexpected new test in testsuite.") + + self.tests.extend(tests) + self.tests.sort(key=lambda test: test.classname) + + if self.options.num_parts < 1: + raise RuntimeError("Tests must be split in positive number of parts.") + if self.options.num_parts > len(self.tests): + raise RuntimeError("Cannot have more parts then there exist tests.") + if self.options.part_index < 1 or self.options.part_index > self.options.num_parts: + raise RuntimeError("Part index is out of range") + + self.tests = self._split_tests(self.options.num_parts)[self.options.part_index - 1] + return self.tests + + def _tester_needed(self, tester): + for testsuite in self.options.testsuites: + if tester.name in testsuite.TEST_MANAGER: + return True + return False + + def server_wrapper(self, ready): + self.server = GstValidateTCPServer( + ('localhost', 0), GstValidateListener) + self.server.socket.settimeout(None) + self.server.launcher = self + self.serverport = self.server.socket.getsockname()[1] + self.info("%s server port: %s" % (self, self.serverport)) + ready.set() + + self.server.serve_forever(poll_interval=0.05) + + def _start_server(self): + self.info("Starting TCP Server") + ready = threading.Event() + self.server_thread = threading.Thread(target=self.server_wrapper, + kwargs={'ready': ready}) + self.server_thread.start() + ready.wait() + os.environ["GST_VALIDATE_SERVER"] = "tcp://localhost:%s" % self.serverport + + def _stop_server(self): + if self.server: + self.server.shutdown() + self.server_thread.join() + self.server.server_close() + self.server = None + + def test_wait(self): + while True: + # Check process every second for timeout + try: + self.queue.get(timeout=1) + except queue.Empty: + pass + + for test in self.jobs: + if test.process_update(): + self.jobs.remove(test) + return test + + def tests_wait(self): + try: + test = self.test_wait() + test.check_results() + except KeyboardInterrupt: + for test in self.jobs: + test.kill_subprocess() + raise + + return test + + def start_new_job(self, tests_left): + try: + test = tests_left.pop(0) + except IndexError: + return False + + test.test_start(self.queue) + + self.jobs.append(test) + + return True + + def print_result(self, current_test_num, test, retry_on_failure=False): + if test.result != Result.PASSED and not retry_on_failure: + printc(str(test), color=utils.get_color_for_result(test.result)) + + length = 80 + progress = int(length * current_test_num // self.total_num_tests) + bar = '█' * progress + '-' * (length - progress) + if is_tty(): + printc('\r|%s| [%s/%s]' % (bar, current_test_num, self.total_num_tests), end='\r') + else: + if progress > self.current_progress: + self.current_progress = progress + printc('|%s| [%s/%s]' % (bar, current_test_num, self.total_num_tests)) + + def _run_tests(self, running_tests=None, all_alone=False, retry_on_failures=False): + if not self.all_tests: + self.all_tests = self.list_tests() + + if not running_tests: + running_tests = self.tests + + self.reporter.init_timer() + alone_tests = [] + tests = [] + for test in running_tests: + if test.is_parallel and not all_alone: + tests.append(test) + else: + alone_tests.append(test) + + # use max to defend against the case where all tests are alone_tests + max_num_jobs = max(min(self.options.num_jobs, len(tests)), 1) + jobs_running = 0 + + if self.options.forever and len(tests) < self.options.num_jobs and len(tests): + max_num_jobs = self.options.num_jobs + copied = [] + i = 0 + while (len(tests) + len(copied)) < max_num_jobs: + copied.append(tests[i].copy(len(copied) + 1)) + + i += 1 + if i >= len(tests): + i = 0 + tests += copied + self.tests += copied + + self.total_num_tests = len(self.all_tests) + printc("\nRunning %d tests..." % self.total_num_tests, color=Colors.HEADER) + # if order of test execution doesn't matter, shuffle + # the order to optimize cpu usage + if self.options.shuffle: + random.shuffle(tests) + random.shuffle(alone_tests) + + current_test_num = 1 + to_retry = [] + for num_jobs, tests in [(max_num_jobs, tests), (1, alone_tests)]: + tests_left = list(tests) + for i in range(num_jobs): + if not self.start_new_job(tests_left): + break + jobs_running += 1 + + while jobs_running != 0: + test = self.tests_wait() + jobs_running -= 1 + current_test_num += 1 + res = test.test_end(retry_on_failure=retry_on_failures) + to_report = True + if res not in [Result.PASSED, Result.SKIPPED, Result.KNOWN_ERROR]: + if self.options.forever or self.options.fatal_error: + self.print_result(current_test_num - 1, test, retry_on_failure=retry_on_failures) + self.reporter.after_test(test) + return False + + if retry_on_failures: + if not self.options.redirect_logs and test.allow_flakiness: + test.copy_logfiles() + printc(test) + to_retry.append(test) + + # Not adding to final report if flakiness is tolerated + to_report = not test.allow_flakiness + self.print_result(current_test_num - 1, test, retry_on_failure=retry_on_failures) + if to_report: + self.reporter.after_test(test) + if retry_on_failures: + test.clean() + if self.start_new_job(tests_left): + jobs_running += 1 + + if to_retry: + printc("--> Rerunning the following tests to see if they are flaky:", Colors.WARNING) + for test in to_retry: + printc(' * %s' % test.classname) + printc('') + return self._run_tests(to_retry, all_alone=True, retry_on_failures=False) + + return True + + def clean_tests(self, stop_server=False): + for test in self.tests: + test.clean() + if stop_server: + self._stop_server() + + def run_tests(self): + r = 0 + try: + self._start_server() + if self.options.forever: + r = 1 + while True: + printc("-> Iteration %d" % r, end='\r') + + if not self._run_tests(): + break + r += 1 + self.clean_tests() + msg = "-> Iteration %d... %sOK%s" % (r, Colors.OKGREEN, Colors.ENDC) + printc(msg, end="\r") + + return False + elif self.options.n_runs: + res = True + for r in range(self.options.n_runs): + printc("-> Iteration %d" % r, end='\r') + if not self._run_tests(): + res = False + printc("ERROR", Colors.FAIL, end="\r") + else: + printc("OK", Colors.OKGREEN, end="\r") + self.clean_tests() + + return res + else: + return self._run_tests(retry_on_failures=self.options.retry_on_failures) + finally: + if self.options.forever: + printc("\n-> Ran %d times" % r) + if self.httpsrv: + self.httpsrv.stop() + if self.vfb_server: + self.vfb_server.stop() + self.clean_tests(True) + + def final_report(self): + return self.reporter.final_report() + + def needs_http_server(self): + for tester in self.testers: + if tester.needs_http_server(): + return True + + +class NamedDic(object): + + def __init__(self, props): + if props: + for name, value in props.items(): + setattr(self, name, value) + + +class Scenario(object): + + def __init__(self, name, props, path=None): + self.name = name + self.path = path + + for prop, value in props: + setattr(self, prop.replace("-", "_"), value) + + def get_execution_name(self): + if self.path is not None: + return self.path + else: + return self.name + + def seeks(self): + if hasattr(self, "seek"): + return bool(self.seek) + + return False + + def needs_clock_sync(self): + if hasattr(self, "need_clock_sync"): + return bool(self.need_clock_sync) + + return False + + def needs_live_content(self): + # Scenarios that can only be used on live content + if hasattr(self, "live_content_required"): + return bool(self.live_content_required) + return False + + def compatible_with_live_content(self): + # if a live content is required it's implicitly compatible with + # live content + if self.needs_live_content(): + return True + if hasattr(self, "live_content_compatible"): + return bool(self.live_content_compatible) + return False + + def get_min_media_duration(self): + if hasattr(self, "min_media_duration"): + return float(self.min_media_duration) + + return 0 + + def does_reverse_playback(self): + if hasattr(self, "reverse_playback"): + return bool(self.reverse_playback) + + return False + + def get_duration(self): + try: + return float(getattr(self, "duration")) + except AttributeError: + return 0 + + def get_min_tracks(self, track_type): + try: + return int(getattr(self, "min_%s_track" % track_type)) + except AttributeError: + return 0 + + def __repr__(self): + return "" % self.name + + +class ScenarioManager(Loggable): + _instance = None + system_scenarios = [] + special_scenarios = {} + + FILE_EXTENSION = "scenario" + + def __new__(cls, *args, **kwargs): + if not cls._instance: + cls._instance = super(ScenarioManager, cls).__new__( + cls, *args, **kwargs) + cls._instance.config = None + cls._instance.discovered = False + Loggable.__init__(cls._instance) + + return cls._instance + + def find_special_scenarios(self, mfile): + scenarios = [] + mfile_bname = os.path.basename(mfile) + + for f in os.listdir(os.path.dirname(mfile)): + if re.findall("%s\..*\.%s$" % (re.escape(mfile_bname), self.FILE_EXTENSION), f): + scenarios.append(os.path.join(os.path.dirname(mfile), f)) + + if scenarios: + scenarios = self.discover_scenarios(scenarios, mfile) + + return scenarios + + def discover_scenarios(self, scenario_paths=[], mfile=None): + """ + Discover scenarios specified in scenario_paths or the default ones + if nothing specified there + """ + scenarios = [] + scenario_defs = os.path.join(self.config.main_dir, "scenarios.def") + log_path = os.path.join(self.config.logsdir, "scenarios_discovery.log") + logs = open(log_path, 'w') + + try: + command = [GstValidateBaseTestManager.COMMAND, + "--scenarios-defs-output-file", scenario_defs] + command.extend(scenario_paths) + subprocess.check_call(command, stdout=logs, stderr=logs) + except subprocess.CalledProcessError as e: + self.error(e) + self.error('See %s' % log_path) + pass + + config = configparser.RawConfigParser() + f = open(scenario_defs) + config.readfp(f) + + for section in config.sections(): + name = None + if scenario_paths: + for scenario_path in scenario_paths: + if section == scenario_path: + if mfile is None: + name = os.path.basename(section).replace("." + self.FILE_EXTENSION, "") + path = scenario_path + else: + # The real name of the scenario is: + # filename.REALNAME.scenario + name = scenario_path.replace(mfile + ".", "").replace( + "." + self.FILE_EXTENSION, "") + path = scenario_path + break + else: + name = os.path.basename(section).replace("." + self.FILE_EXTENSION, "") + path = None + + assert name + + props = config.items(section) + scenario = Scenario(name, props, path) + if scenario_paths: + self.special_scenarios[path] = scenario + scenarios.append(scenario) + + if not scenario_paths: + self.discovered = True + self.system_scenarios.extend(scenarios) + + return scenarios + + def get_scenario(self, name): + if name is not None and os.path.isabs(name) and name.endswith(self.FILE_EXTENSION): + scenario = self.special_scenarios.get(name) + if scenario: + return scenario + + scenarios = self.discover_scenarios([name]) + self.special_scenarios[name] = scenarios + + if scenarios: + return scenarios[0] + + if self.discovered is False: + self.discover_scenarios() + + if name is None: + return self.system_scenarios + + try: + return [scenario for scenario in self.system_scenarios if scenario.name == name][0] + except IndexError: + self.warning("Scenario: %s not found" % name) + return None + + +class GstValidateBaseTestManager(TestsManager): + scenarios_manager = ScenarioManager() + features_cache = {} + + def __init__(self): + super(GstValidateBaseTestManager, self).__init__() + self._scenarios = [] + self._encoding_formats = [] + + @classmethod + def update_commands(cls, extra_paths=None): + for varname, cmd in {'': 'gst-validate', + 'TRANSCODING_': 'gst-validate-transcoding', + 'MEDIA_CHECK_': 'gst-validate-media-check', + 'RTSP_SERVER_': 'gst-validate-rtsp-server', + 'INSPECT_': 'gst-inspect'}.items(): + setattr(cls, varname + 'COMMAND', which(cmd + '-1.0', extra_paths)) + + @classmethod + def has_feature(cls, featurename): + try: + return cls.features_cache[featurename] + except KeyError: + pass + + try: + subprocess.check_output([cls.INSPECT_COMMAND, featurename]) + res = True + except subprocess.CalledProcessError: + res = False + + cls.features_cache[featurename] = res + return res + + def add_scenarios(self, scenarios): + """ + @scenarios A list or a unic scenario name(s) to be run on the tests. + They are just the default scenarios, and then depending on + the TestsGenerator to be used you can have more fine grained + control on what to be run on each series of tests. + """ + if isinstance(scenarios, list): + self._scenarios.extend(scenarios) + else: + self._scenarios.append(scenarios) + + self._scenarios = list(set(self._scenarios)) + + def set_scenarios(self, scenarios): + """ + Override the scenarios + """ + self._scenarios = [] + self.add_scenarios(scenarios) + + def get_scenarios(self): + return self._scenarios + + def add_encoding_formats(self, encoding_formats): + """ + :param encoding_formats: A list or one single #MediaFormatCombinations describing wanted output + formats for transcoding test. + They are just the default encoding formats, and then depending on + the TestsGenerator to be used you can have more fine grained + control on what to be run on each series of tests. + """ + if isinstance(encoding_formats, list): + self._encoding_formats.extend(encoding_formats) + else: + self._encoding_formats.append(encoding_formats) + + self._encoding_formats = list(set(self._encoding_formats)) + + def get_encoding_formats(self): + return self._encoding_formats + + +GstValidateBaseTestManager.update_commands() + + +class MediaDescriptor(Loggable): + + def __init__(self): + Loggable.__init__(self) + + def get_path(self): + raise NotImplemented + + def has_frames(self): + return False + + def get_framerate(self): + for ttype, caps_str in self.get_tracks_caps(): + if ttype != "video": + continue + + caps = utils.GstCaps.new_from_str(caps_str) + if not caps: + self.warning("Could not create caps for %s" % caps_str) + continue + + framerate = caps[0].get("framerate") + if framerate: + return framerate + + return Fraction(0, 1) + + def get_media_filepath(self): + raise NotImplemented + + def skip_parsers(self): + return False + + def get_caps(self): + raise NotImplemented + + def get_uri(self): + raise NotImplemented + + def get_duration(self): + raise NotImplemented + + def get_protocol(self): + raise NotImplemented + + def is_seekable(self): + raise NotImplemented + + def is_live(self): + raise NotImplemented + + def is_image(self): + raise NotImplemented + + def get_num_tracks(self, track_type): + raise NotImplemented + + def get_tracks_caps(self): + return [] + + def can_play_reverse(self): + raise NotImplemented + + def prerrols(self): + return True + + def is_compatible(self, scenario): + if scenario is None: + return True + + if scenario.seeks() and (not self.is_seekable() or self.is_image()): + self.debug("Do not run %s as %s does not support seeking", + scenario, self.get_uri()) + return False + + if self.is_image() and scenario.needs_clock_sync(): + self.debug("Do not run %s as %s is an image", + scenario, self.get_uri()) + return False + + if not self.can_play_reverse() and scenario.does_reverse_playback(): + return False + + if not self.is_live() and scenario.needs_live_content(): + self.debug("Do not run %s as %s is not a live content", + scenario, self.get_uri()) + return False + + if self.is_live() and not scenario.compatible_with_live_content(): + self.debug("Do not run %s as %s is a live content", + scenario, self.get_uri()) + return False + + if not self.prerrols() and getattr(scenario, 'needs_preroll', False): + return False + + if self.get_duration() and self.get_duration() / GST_SECOND < scenario.get_min_media_duration(): + self.debug( + "Do not run %s as %s is too short (%i < min media duation : %i", + scenario, self.get_uri(), + self.get_duration() / GST_SECOND, + scenario.get_min_media_duration()) + return False + + for track_type in ['audio', 'subtitle', 'video']: + if self.get_num_tracks(track_type) < scenario.get_min_tracks(track_type): + self.debug("%s -- %s | At least %s %s track needed < %s" + % (scenario, self.get_uri(), track_type, + scenario.get_min_tracks(track_type), + self.get_num_tracks(track_type))) + return False + + return True + + +class GstValidateMediaDescriptor(MediaDescriptor): + # Some extension file for discovering results + SKIPPED_MEDIA_INFO_EXT = "media_info.skipped" + MEDIA_INFO_EXT = "media_info" + PUSH_MEDIA_INFO_EXT = "media_info.push" + STREAM_INFO_EXT = "stream_info" + + __all_descriptors = {} + + @classmethod + def get(cls, xml_path): + if xml_path in cls.__all_descriptors: + return cls.__all_descriptors[xml_path] + return GstValidateMediaDescriptor(xml_path) + + def __init__(self, xml_path): + super(GstValidateMediaDescriptor, self).__init__() + + self._media_file_path = None + main_descriptor = self.__all_descriptors.get(xml_path) + if main_descriptor: + self._copy_data_from_main(main_descriptor) + else: + self.__all_descriptors[xml_path] = self + + self._xml_path = xml_path + try: + media_xml = ET.parse(xml_path).getroot() + except xml.etree.ElementTree.ParseError: + printc("Could not parse %s" % xml_path, + Colors.FAIL) + raise + self._extract_data(media_xml) + + self.set_protocol(urllib.parse.urlparse(self.get_uri()).scheme) + + def skip_parsers(self): + return self._skip_parsers + + def has_frames(self): + return self._has_frames + + def _copy_data_from_main(self, main_descriptor): + for attr in main_descriptor.__dict__.keys(): + setattr(self, attr, getattr(main_descriptor, attr)) + + def _extract_data(self, media_xml): + # Extract the information we need from the xml + self._caps = media_xml.findall("streams")[0].attrib["caps"] + self._track_caps = [] + try: + streams = media_xml.findall("streams")[0].findall("stream") + except IndexError: + pass + else: + for stream in streams: + self._track_caps.append( + (stream.attrib["type"], stream.attrib["caps"])) + + self._skip_parsers = bool(int(media_xml.attrib.get('skip-parsers', 0))) + self._has_frames = bool(int(media_xml.attrib["frame-detection"])) + self._duration = int(media_xml.attrib["duration"]) + self._uri = media_xml.attrib["uri"] + parsed_uri = urllib.parse.urlparse(self.get_uri()) + self._protocol = media_xml.get("protocol", parsed_uri.scheme) + if parsed_uri.scheme == "file": + if not os.path.exists(parsed_uri.path) and os.path.exists(self.get_media_filepath()): + self._uri = "file://" + self.get_media_filepath() + elif parsed_uri.scheme == Protocols.IMAGESEQUENCE: + self._media_file_path = os.path.join(os.path.dirname(self.__cleanup_media_info_ext()), os.path.basename(parsed_uri.path)) + self._uri = parsed_uri._replace(path=os.path.join(os.path.dirname(self.__cleanup_media_info_ext()), os.path.basename(self._media_file_path))).geturl() + self._is_seekable = media_xml.attrib["seekable"].lower() == "true" + self._is_live = media_xml.get("live", "false").lower() == "true" + self._is_image = False + for stream in media_xml.findall("streams")[0].findall("stream"): + if stream.attrib["type"] == "image": + self._is_image = True + self._track_types = [] + for stream in media_xml.findall("streams")[0].findall("stream"): + self._track_types.append(stream.attrib["type"]) + + def __cleanup_media_info_ext(self): + for ext in [self.MEDIA_INFO_EXT, self.PUSH_MEDIA_INFO_EXT, self.STREAM_INFO_EXT, + self.SKIPPED_MEDIA_INFO_EXT, ]: + if self._xml_path.endswith(ext): + return self._xml_path[:len(self._xml_path) - (len(ext) + 1)] + + assert "Not reached" == None # noqa + + @staticmethod + def new_from_uri(uri, verbose=False, include_frames=False, is_push=False, is_skipped=False): + """ + include_frames = 0 # Never + include_frames = 1 # always + include_frames = 2 # if previous file included them + + """ + media_path = utils.url2path(uri) + + ext = GstValidateMediaDescriptor.MEDIA_INFO_EXT + if is_push: + ext = GstValidateMediaDescriptor.PUSH_MEDIA_INFO_EXT + elif is_skipped: + ext = GstValidateMediaDescriptor.SKIPPED_MEDIA_INFO_EXT + descriptor_path = "%s.%s" % (media_path, ext) + args = GstValidateBaseTestManager.MEDIA_CHECK_COMMAND.split(" ") + if include_frames == 2: + try: + media_xml = ET.parse(descriptor_path).getroot() + prev_uri = urllib.parse.urlparse(media_xml.attrib['uri']) + if prev_uri.scheme == Protocols.IMAGESEQUENCE: + parsed_uri = urllib.parse.urlparse(uri) + uri = prev_uri._replace(path=os.path.join(os.path.dirname(parsed_uri.path), os.path.basename(prev_uri.path))).geturl() + include_frames = bool(int(media_xml.attrib["frame-detection"])) + if bool(int(media_xml.attrib.get("skip-parsers", 0))): + args.append("--skip-parsers") + except FileNotFoundError: + pass + else: + include_frames = bool(include_frames) + args.append(uri) + + args.extend(["--output-file", descriptor_path]) + if include_frames: + args.extend(["--full"]) + + if verbose: + printc("Generating media info for %s\n" + " Command: '%s'" % (media_path, ' '.join(args)), + Colors.OKBLUE) + + try: + subprocess.check_output(args, stderr=open(os.devnull)) + except subprocess.CalledProcessError as e: + if verbose: + printc("Result: Failed", Colors.FAIL) + else: + loggable.warning("GstValidateMediaDescriptor", + "Exception: %s" % e) + return None + + if verbose: + printc("Result: Passed", Colors.OKGREEN) + + try: + return GstValidateMediaDescriptor(descriptor_path) + except (IOError, xml.etree.ElementTree.ParseError): + return None + + def get_path(self): + return self._xml_path + + def need_clock_sync(self): + return Protocols.needs_clock_sync(self.get_protocol()) + + def get_media_filepath(self): + if self._media_file_path is None: + self._media_file_path = self.__cleanup_media_info_ext() + return self._media_file_path + + def get_caps(self): + return self._caps + + def get_tracks_caps(self): + return self._track_caps + + def get_uri(self): + return self._uri + + def get_duration(self): + return self._duration + + def set_protocol(self, protocol): + if self._xml_path.endswith(GstValidateMediaDescriptor.PUSH_MEDIA_INFO_EXT): + self._protocol = Protocols.PUSHFILE + else: + self._protocol = protocol + + def get_protocol(self): + return self._protocol + + def is_seekable(self): + return self._is_seekable + + def is_live(self): + return self._is_live + + def can_play_reverse(self): + return True + + def is_image(self): + return self._is_image + + def get_num_tracks(self, track_type): + n = 0 + for t in self._track_types: + if t == track_type: + n += 1 + + return n + + def get_clean_name(self): + name = os.path.basename(self.get_path()) + regex = '|'.join(['\\.%s$' % ext for ext in [self.SKIPPED_MEDIA_INFO_EXT, self.MEDIA_INFO_EXT, self.PUSH_MEDIA_INFO_EXT, self.STREAM_INFO_EXT]]) + name = re.sub(regex, "", name) + + return name.replace('.', "_") + + +class MediaFormatCombination(object): + FORMATS = {"aac": "audio/mpeg,mpegversion=4", # Audio + "ac3": "audio/x-ac3", + "vorbis": "audio/x-vorbis", + "mp3": "audio/mpeg,mpegversion=1,layer=3", + "opus": "audio/x-opus", + "rawaudio": "audio/x-raw", + + # Video + "h264": "video/x-h264", + "h265": "video/x-h265", + "vp8": "video/x-vp8", + "vp9": "video/x-vp9", + "theora": "video/x-theora", + "prores": "video/x-prores", + "jpeg": "image/jpeg", + + # Containers + "webm": "video/webm", + "ogg": "application/ogg", + "mkv": "video/x-matroska", + "mp4": "video/quicktime,variant=iso;", + "quicktime": "video/quicktime;"} + + def __str__(self): + return "%s and %s in %s" % (self.audio, self.video, self.container) + + def __init__(self, container, audio, video, duration_factor=1, + video_restriction=None, audio_restriction=None): + """ + Describes a media format to be used for transcoding tests. + + :param container: A string defining the container format to be used, must bin in self.FORMATS + :param audio: A string defining the audio format to be used, must bin in self.FORMATS + :param video: A string defining the video format to be used, must bin in self.FORMATS + """ + self.container = container + self.audio = audio + self.video = video + self.video_restriction = video_restriction + self.audio_restriction = audio_restriction + + def get_caps(self, track_type): + try: + return self.FORMATS[self.__dict__[track_type]] + except KeyError: + return None + + def get_audio_caps(self): + return self.get_caps("audio") + + def get_video_caps(self): + return self.get_caps("video") + + def get_muxer_caps(self): + return self.get_caps("container") diff --git a/validate/launcher/config.py.in b/validate/launcher/config.py.in new file mode 100644 index 0000000000..f0e3c22cf3 --- /dev/null +++ b/validate/launcher/config.py.in @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2015,Thibault Saunier +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +LIBDIR = r'@LIBDIR@' +DATADIR = r'@DATADIR@' +BUILDDIR = r'@BUILDDIR@' +SRCDIR = r'@SRCDIR@' +GST_VALIDATE_TESTSUITE_VERSION = '@GST_VALIDATE_TESTSUITE_VERSION@' diff --git a/validate/launcher/httpserver.py b/validate/launcher/httpserver.py new file mode 100644 index 0000000000..f1fe2970ae --- /dev/null +++ b/validate/launcher/httpserver.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2013,Thibault Saunier +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +import os +import time +from . import loggable +import subprocess +import sys +import urllib.request +import urllib.error +import urllib.parse + +from .utils import printc, Colors + +logcat = "httpserver" + + +class HTTPServer(loggable.Loggable): + + """ Class to run a SimpleHttpServer in a process.""" + + def __init__(self, options): + loggable.Loggable.__init__(self) + self.options = options + self._process = None + self._logsfile = None + + def _check_is_up(self, timeout=60): + """ Check if the server is up, running a simple test based on wget. """ + start = time.time() + while True: + try: + response = urllib.request.urlopen('http://127.0.0.1:%s' % ( + self.options.http_server_port)) + return True + except urllib.error.URLError as e: + pass + + if time.time() - start > timeout: + return False + + time.sleep(1) + + def start(self): + """ Start the server in a subprocess """ + self._logsfile = open(os.path.join(self.options.logsdir, + "httpserver.logs"), 'w+') + if self.options.http_server_dir is not None: + if self._check_is_up(timeout=2): + return True + + printc("-> Starting HTTP server... ", end='') + try: + self.debug("Launching http server") + cmd = "%s %s %d %s" % (sys.executable, os.path.join(os.path.dirname(__file__), + "RangeHTTPServer.py"), + self.options.http_server_port, + self.options.http_bandwith, + ) + curdir = os.path.abspath(os.curdir) + os.chdir(self.options.http_server_dir) + # cmd = "twistd -no web --path=%s -p %d" % ( + # self.options.http_server_dir, self.options.http_server_port) + self.debug( + "Launching server: %s (logs in %s)", cmd, self._logsfile) + self._process = subprocess.Popen(cmd.split(" "), + stderr=self._logsfile, + stdout=self._logsfile) + os.chdir(curdir) + self.debug("Launched http server") + # Dirty way to avoid eating to much CPU... + # good enough for us anyway. + time.sleep(1) + + if self._check_is_up(): + printc("OK", Colors.OKGREEN) + return True + else: + printc("FAILURE", Colors.FAIL) + self._process.terminate() + self._process = None + except OSError as ex: + printc("FAILURE", Colors.FAIL) + self.warning(logcat, "Could not launch server %s" % ex) + + return False + + def stop(self): + """ Stop the server subprocess if running. """ + if self._process: + self._process.terminate() + self._process = None + self.debug("Server stopped") diff --git a/validate/launcher/loggable.py b/validate/launcher/loggable.py new file mode 100644 index 0000000000..404fd69022 --- /dev/null +++ b/validate/launcher/loggable.py @@ -0,0 +1,1055 @@ +# -*- coding: utf-8 -*- +# Pitivi video editor +# Copyright (c) 2009, Alessandro Decina +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. +import collections +import errno +import fnmatch +import os +import re +import sys +import threading +import time +import traceback +import types + + +# environment variables controlling levels for each category +_DEBUG = "*:1" +# name of the environment variable controlling our logging +_ENV_VAR_NAME = None +# package names we should scrub filenames for +_PACKAGE_SCRUB_LIST = [] + +# dynamic dictionary of categories already seen and their level +_categories = {} + +# log handlers registered +_log_handlers = [] +_log_handlers_limited = [] + +_initialized = False + +_stdout = None +_stderr = None +_old_hup_handler = None +_outfile = None + + +# public log levels +(ERROR, + WARN, + FIXME, + INFO, + DEBUG, + LOG) = list(range(1, 7)) + +COLORS = {ERROR: 'RED', + WARN: 'YELLOW', + FIXME: 'MAGENTA', + INFO: 'GREEN', + DEBUG: 'BLUE', + LOG: 'CYAN'} + +_FORMATTED_LEVELS = [] +_LEVEL_NAMES = ['ERROR', 'WARN', 'FIXME', 'INFO', 'DEBUG', 'LOG'] + + +class TerminalController: + """A class for generating formatted output to a terminal. + + `TerminalController` defines a set of instance variables whose + values are initialized to the control sequence necessary to + perform a given action. These can be simply included in normal + output to the terminal: + + >>> term = TerminalController() + >>> print('This is '+term.GREEN+'green'+term.NORMAL) + + Alternatively, the `render()` method can used, which replaces + '${action}' with the string required to perform 'action': + + >>> term = TerminalController() + >>> print(term.render('This is ${GREEN}green${NORMAL}')) + + If the terminal doesn't support a given action, then the value of + the corresponding instance variable will be set to ''. As a + result, the above code will still work on terminals that do not + support color, except that their output will not be colored. + Also, this means that you can test whether the terminal supports a + given action by simply testing the truth value of the + corresponding instance variable: + + >>> term = TerminalController() + >>> if term.CLEAR_SCREEN: + ... print('This terminal supports clearning the screen.') + + Finally, if the width and height of the terminal are known, then + they will be stored in the `COLS` and `LINES` attributes. + + Args: + term_stream (Optional): The stream that will be used for terminal + output; if this stream is not a tty, then the terminal is + assumed to be a dumb terminal (i.e., have no capabilities). + """ + # Cursor movement: + BOL = '' # : Move the cursor to the beginning of the line + UP = '' # : Move the cursor up one line + DOWN = '' # : Move the cursor down one line + LEFT = '' # : Move the cursor left one char + RIGHT = '' # : Move the cursor right one char + + # Deletion: + CLEAR_SCREEN = '' # : Clear the screen and move to home position + CLEAR_EOL = '' # : Clear to the end of the line. + CLEAR_BOL = '' # : Clear to the beginning of the line. + CLEAR_EOS = '' # : Clear to the end of the screen + + # Output modes: + BOLD = '' # : Turn on bold mode + BLINK = '' # : Turn on blink mode + DIM = '' # : Turn on half-bright mode + REVERSE = '' # : Turn on reverse-video mode + NORMAL = '' # : Turn off all modes + + # Cursor display: + HIDE_CURSOR = '' # : Make the cursor invisible + SHOW_CURSOR = '' # : Make the cursor visible + + # Terminal size: + COLS = None # : Width of the terminal (None for unknown) + LINES = None # : Height of the terminal (None for unknown) + + # Foreground colors: + BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = '' + + # Background colors: + BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = '' + BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = '' + + _STRING_CAPABILITIES = """ + BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1 + CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold + BLINK=blink DIM=dim REVERSE=rev UNDERLINE=smul NORMAL=sgr0 + HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm""".split() + _COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split() + _ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split() + + def __init__(self, term_stream=sys.stdout): + # Curses isn't available on all platforms + try: + import curses + except ImportError: + return + + # If the stream isn't a tty, then assume it has no capabilities. + if not term_stream.isatty(): + return + + # Check the terminal type. If we fail, then assume that the + # terminal has no capabilities. + try: + curses.setupterm() + except BaseException: + return + + # Look up numeric capabilities. + self.COLS = curses.tigetnum('cols') + self.LINES = curses.tigetnum('lines') + + # Look up string capabilities. + for capability in self._STRING_CAPABILITIES: + (attrib, cap_name) = capability.split('=') + setattr(self, attrib, self._tigetstr(cap_name) or b'') + + # Colors + set_fg = self._tigetstr('setf') + if set_fg: + for i, color in zip(list(range(len(self._COLORS))), self._COLORS): + setattr(self, color, curses.tparm(set_fg, i) or b'') + set_fg_ansi = self._tigetstr('setaf') + if set_fg_ansi: + for i, color in zip(list(range(len(self._ANSICOLORS))), + self._ANSICOLORS): + setattr(self, color, curses.tparm(set_fg_ansi, i) or b'') + set_bg = self._tigetstr('setb') + if set_bg: + for i, color in zip(list(range(len(self._COLORS))), self._COLORS): + setattr(self, 'BG_' + color, curses.tparm(set_bg, i) or b'') + set_bg_ansi = self._tigetstr('setab') + if set_bg_ansi: + for i, color in zip(list(range(len(self._ANSICOLORS))), + self._ANSICOLORS): + setattr( + self, 'BG_' + color, curses.tparm(set_bg_ansi, i) or b'') + + def _tigetstr(self, cap_name): + # String capabilities can include "delays" of the form "$<2>". + # For any modern terminal, we should be able to just ignore + # these, so strip them out. + import curses + cap = curses.tigetstr(cap_name) or b'' + return re.sub(r'\$<\d+>[/*]?', '', cap.decode()).encode() + + def render(self, template): + """Replaces each $-substitutions in the specified template string. + + The placeholders are replaced with the corresponding terminal control + string (if it's defined) or '' (if it's not). + """ + return re.sub(r'\$\$|\${\w+}', self._render_sub, template) + + def _render_sub(self, match): + s = match.group() + if s == '$$': + return s + else: + return getattr(self, s[2:-1]) + +# +# Example use case: progress bar +# + + +class ProgressBar: + """A 3-line progress bar. + + Looks like this: + + Header + 20% [===========----------------------------------] + progress message + + The progress bar is colored, if the terminal supports color + output; and adjusts to the width of the terminal. + """ + + BAR = '%3d%% ${GREEN}[${BOLD}%s%s${NORMAL}${GREEN}]${NORMAL}\n' + HEADER = '${BOLD}${CYAN}%s${NORMAL}\n\n' + + def __init__(self, term, header): + self.term = term + if not (self.term.CLEAR_EOL and self.term.UP and self.term.BOL): + raise ValueError("Terminal isn't capable enough -- you " + "should use a simpler progress dispaly.") + self.width = self.term.COLS or 75 + self.bar = term.render(self.BAR) + self.header = self.term.render(self.HEADER % header.center(self.width)) + self.cleared = 1 # : true if we haven't drawn the bar yet. + self.update(0, '') + + def update(self, percent, message): + if self.cleared: + sys.stdout.write(self.header) + self.cleared = 0 + n = int((self.width - 10) * percent) + sys.stdout.write( + self.term.BOL + self.term.UP + self.term.CLEAR_EOL + + (self.bar % (100 * percent, '=' * n, '-' * (self.width - 10 - n))) + + self.term.CLEAR_EOL + message.center(self.width)) + + def clear(self): + if not self.cleared: + sys.stdout.write(self.term.BOL + self.term.CLEAR_EOL + + self.term.UP + self.term.CLEAR_EOL + + self.term.UP + self.term.CLEAR_EOL) + self.cleared = 1 + + +def getLevelName(level): + """Returns the name of the specified log level. + + Args: + level (int): The level we want to know the name. + + Returns: + str: The name of the level. + """ + assert isinstance(level, int) and level > 0 and level < 7, \ + TypeError("Bad debug level") + return getLevelNames()[level - 1] + + +def getLevelNames(): + """Returns a list with the level names. + + Returns: + List[str]: A list with the level names. + """ + return _LEVEL_NAMES + + +def getLevelInt(levelName): + """Returns the integer value of the levelName. + + Args: + levelName (str): The string value of the level name. + + Returns: + int: The value of the level name we are interested in. + """ + assert isinstance(levelName, str) and levelName in getLevelNames(), \ + "Bad debug level name" + return getLevelNames().index(levelName) + 1 + + +def getFormattedLevelName(level): + assert isinstance(level, int) and level > 0 and level < len(_LEVEL_NAMES) + 1, \ + TypeError("Bad debug level") + return _FORMATTED_LEVELS[level - 1] + + +def registerCategory(category): + """Registers the specified category in the debug system. + + A level will be assigned to it based on previous calls to setDebug. + """ + # parse what level it is set to based on _DEBUG + # example: *:2,admin:4 + global _DEBUG + global _levels + global _categories + + level = 0 + chunks = _DEBUG.split(',') + for chunk in chunks: + if not chunk: + continue + if ':' in chunk: + spec, value = chunk.split(':') + else: + spec = '*' + value = chunk + + # our glob is unix filename style globbing, so cheat with fnmatch + # fnmatch.fnmatch didn't work for this, so don't use it + if category in fnmatch.filter((category, ), spec): + # we have a match, so set level based on string or int + if not value: + continue + try: + level = int(value) + except ValueError: # e.g. *; we default to most + level = 5 + # store it + _categories[category] = level + + +def getCategoryLevel(category): + """Gets the debug level at which the specified category is being logged. + + Registers the category and thus assigns a log level if it wasn't registered + yet. + + Args: + category (string): The category we are interested in. + """ + global _categories + if category not in _categories: + registerCategory(category) + return _categories[category] + + +def setLogSettings(state): + """Updates the current log settings. + + This can restore an old saved log settings object returned by + getLogSettings. + + Args: + state: The settings to set. + """ + + global _DEBUG + global _log_handlers + global _log_handlers_limited + + (_DEBUG, + _categories, + _log_handlers, + _log_handlers_limited) = state + + for category in _categories: + registerCategory(category) + + +def getLogSettings(): + """Fetches the current log settings. + + The returned object can be sent to setLogSettings to restore the + returned settings + + Returns: + The current settings. + """ + return (_DEBUG, + _categories, + _log_handlers, + _log_handlers_limited) + + +def _canShortcutLogging(category, level): + if _log_handlers: + # we have some loggers operating without filters, have to do + # everything + return False + else: + return level > getCategoryLevel(category) + + +def scrubFilename(filename): + ''' + Scrub the filename to a relative path for all packages in our scrub list. + ''' + global _PACKAGE_SCRUB_LIST + for package in _PACKAGE_SCRUB_LIST: + i = filename.rfind(package) + if i > -1: + return filename[i:] + + return filename + + +def getFileLine(where=-1): + """Returns the filename and line number for the specified location. + + Args: + where(int or function): If it's a (negative) integer, looks for + the code entry in the current stack that is the given number + of frames above this module. + If it's a function, look for the code entry of the function. + + Returns: + str, int, str: file, line, function_name. + """ + co = None + lineno = None + name = None + + if isinstance(where, types.FunctionType): + co = where.__code__ + lineno = co.co_firstlineno + name = co.co_name + elif isinstance(where, types.MethodType): + co = where.__func__.__code__ + lineno = co.co_firstlineno + name = co.co_name + else: + stackFrame = sys._getframe() + while stackFrame: + co = stackFrame.f_code + if not co.co_filename.endswith('loggable.py'): + co = stackFrame.f_code + lineno = stackFrame.f_lineno + name = co.co_name + break + stackFrame = stackFrame.f_back + + if not co: + return "", 0, None + + return scrubFilename(co.co_filename), lineno, name + + +def ellipsize(o): + """Ellipsizes the representation of the given object.""" + r = repr(o) + if len(r) < 800: + return r + + r = r[:60] + ' ... ' + r[-15:] + return r + + +def getFormatArgs(startFormat, startArgs, endFormat, endArgs, args, kwargs): + """Creates a format and args to use for logging. + + This avoids needlessly interpolating variables. + """ + debugArgs = startArgs[:] + for a in args: + debugArgs.append(ellipsize(a)) + + for items in list(kwargs.items()): + debugArgs.extend(items) + debugArgs.extend(endArgs) + format = startFormat \ + + ', '.join(('%s', ) * len(args)) \ + + (kwargs and ', ' or '') \ + + ', '.join(('%s=%r', ) * len(kwargs)) \ + + endFormat + return format, debugArgs + + +def doLog(level, object, category, format, args, where=-1, filePath=None, line=None): + """Logs something. + + Args: + where (int or function): What to log file and line number for; + -1 for one frame above log.py; -2 and down for higher up; + a function for a (future) code object. + filePath (Optional[str]): The file to show the message as coming from, + if caller knows best. + line (Optional[int]): The line to show the message as coming from, + if caller knows best. + + Returns: + A dict of calculated variables, if they needed calculating. + currently contains file and line; this prevents us from + doing this work in the caller when it isn't needed because + of the debug level. + """ + ret = {} + + if args: + message = format % args + else: + message = format + funcname = None + + if level > getCategoryLevel(category): + handlers = _log_handlers + else: + handlers = _log_handlers + _log_handlers_limited + + if handlers: + if filePath is None and line is None: + (filePath, line, funcname) = getFileLine(where=where) + ret['filePath'] = filePath + ret['line'] = line + if funcname: + message = "\033[00m\033[32;01m%s:\033[00m %s" % (funcname, message) + for handler in handlers: + try: + handler(level, object, category, filePath, line, message) + except TypeError as e: + raise SystemError("handler %r raised a TypeError: %s" % ( + handler, getExceptionMessage(e))) + + return ret + + +def errorObject(object, cat, format, *args): + """Logs a fatal error message in the specified category. + + This will also raise a `SystemExit`. + """ + doLog(ERROR, object, cat, format, args) + + +def warningObject(object, cat, format, *args): + """Logs a warning message in the specified category. + + This is used for non-fatal problems. + """ + doLog(WARN, object, cat, format, args) + + +def fixmeObject(object, cat, format, *args): + """Logs a fixme message in the specified category. + + This is used for not implemented codepaths or known issues in the code. + """ + doLog(FIXME, object, cat, format, args) + + +def infoObject(object, cat, format, *args): + """Logs an informational message in the specified category.""" + doLog(INFO, object, cat, format, args) + + +def debugObject(object, cat, format, *args): + """Logs a debug message in the specified category.""" + doLog(DEBUG, object, cat, format, args) + + +def logObject(object, cat, format, *args): + """Logs a log message. + + Used for debugging recurring events. + """ + doLog(LOG, object, cat, format, args) + + +def safeprintf(file, format, *args): + """Writes to a file object, ignoring errors.""" + try: + if args: + file.write(format % args) + else: + file.write(format) + except IOError as e: + if e.errno == errno.EPIPE: + # if our output is closed, exit; e.g. when logging over an + # ssh connection and the ssh connection is closed + os._exit(os.EX_OSERR) + # otherwise ignore it, there's nothing you can do + + +def printHandler(level, object, category, file, line, message): + """Writes to stderr. + + The output will be different depending the value of "_enableCrackOutput"; + in Pitivi's case, that is True when the GST_DEBUG env var is defined. + + Args: + level (str): + object (str): Can be None. + category (str): + message (str): + """ + global _outfile + + # Make the file path more compact for readability + file = os.path.relpath(file) + where = "(%s:%d)" % (file, line) + + # If GST_DEBUG is not set, we can assume only PITIVI_DEBUG is set, so don't + # show a bazillion of debug details that are not relevant to Pitivi. + if not _enableCrackOutput: + safeprintf(_outfile, '%s %-8s %-17s %-2s %s %s\n', + getFormattedLevelName(level), time.strftime("%H:%M:%S"), + category, object, message, where) + else: + o = "" + if object: + o = '"' + object + '"' + # level pid object cat time + # 5 + 1 + 7 + 1 + 32 + 1 + 17 + 1 + 15 == 80 + safeprintf( + _outfile, '%s [%5d] [0x%12x] %-32s %-17s %-15s %-4s %s %s\n', + getFormattedLevelName(level), os.getpid(), + threading.current_thread().ident, + o[:32], category, time.strftime("%b %d %H:%M:%S"), "", + message, where) + _outfile.flush() + + +def logLevelName(level): + format = '%-5s' + return format % (_LEVEL_NAMES[level - 1], ) + + +def _as_string(string_or_bytes): + return string_or_bytes.decode() if isinstance(string_or_bytes, bytes) else string_or_bytes + + +def _preformatLevels(enableColorOutput): + terminal_controller = TerminalController() + for level in ERROR, WARN, FIXME, INFO, DEBUG, LOG: + if enableColorOutput: + formatter = ''.join( + (_as_string(terminal_controller.BOLD), + _as_string(getattr(terminal_controller, COLORS[level])), + logLevelName(level), + _as_string(terminal_controller.NORMAL))) + else: + formatter = logLevelName(level) + _FORMATTED_LEVELS.append(formatter) + +# "public" useful API + +# setup functions + + +def init(envVarName, enableColorOutput=True, enableCrackOutput=True): + """Initializes the logging system. + + Needs to be called before using the log methods. + + Args: + envVarName (str): The name of the environment variable with additional + settings. + """ + global _initialized + global _outfile + global _enableCrackOutput + _enableCrackOutput = enableCrackOutput + + if _initialized: + return + + global _ENV_VAR_NAME + _ENV_VAR_NAME = envVarName + + _preformatLevels(enableColorOutput) + + if envVarName in os.environ: + # install a log handler that uses the value of the environment var + setDebug(os.environ[envVarName]) + filenameEnvVarName = envVarName + "_FILE" + + if filenameEnvVarName in os.environ: + # install a log handler that uses the value of the environment var + _outfile = open(os.environ[filenameEnvVarName], "w+") + else: + _outfile = sys.stderr + + addLimitedLogHandler(printHandler) + + _initialized = True + + +def setDebug(string): + """Sets the DEBUG string. + + This controls the log output. + """ + global _DEBUG + global _ENV_VAR_NAME + global _categories + + _DEBUG = string + debug('log', "%s set to %s" % (_ENV_VAR_NAME, _DEBUG)) + + # reparse all already registered category levels + for category in _categories: + registerCategory(category) + + +def getDebug(): + """Returns the currently active DEBUG string.""" + global _DEBUG + return _DEBUG + + +def setPackageScrubList(*packages): + """Sets the package names to scrub from filenames. + + Filenames from these paths in log messages will be scrubbed to their + relative file path instead of the full absolute path. + + Args: + *packages (List[str]): The packages names to scrub. + """ + global _PACKAGE_SCRUB_LIST + _PACKAGE_SCRUB_LIST = packages + + +def reset(): + """Resets the logging system, removing all log handlers.""" + global _log_handlers, _log_handlers_limited, _initialized + + _log_handlers = [] + _log_handlers_limited = [] + _initialized = False + + +def addLogHandler(func): + """Adds a custom log handler. + + The log handler receives all the log messages. + + Args: + func (function): A function object with prototype + (level, object, category, message) where level is either + ERROR, WARN, INFO, DEBUG, or LOG, and the rest of the arguments are + strings or None. Use getLevelName(level) to get a printable name + for the log level. + + Raises: + TypeError: When func is not a callable. + """ + + if not isinstance(func, collections.Callable): + raise TypeError("func must be callable") + + if func not in _log_handlers: + _log_handlers.append(func) + + +def addLimitedLogHandler(func): + """Adds a custom limited log handler. + + The log handler receives only the messages passing the filter. + + Args: + func (function): A function object with prototype + (level, object, category, message) where level is either + ERROR, WARN, INFO, DEBUG, or LOG, and the rest of the arguments are + strings or None. Use getLevelName(level) to get a printable name + for the log level. + + Raises: + TypeError: When func is not a callable. + """ + if not isinstance(func, collections.Callable): + raise TypeError("func must be callable") + + if func not in _log_handlers_limited: + _log_handlers_limited.append(func) + + +def removeLogHandler(func): + """Removes a registered log handler. + + Raises: + ValueError: When func is not registered. + """ + _log_handlers.remove(func) + + +def removeLimitedLogHandler(func): + """Removes a registered limited log handler. + + Raises: + ValueError: When func is not registered. + """ + _log_handlers_limited.remove(func) + +# public log functions + + +def error(cat, format, *args): + errorObject(None, cat, format, *args) + + +def warning(cat, format, *args): + warningObject(None, cat, format, *args) + + +def fixme(cat, format, *args): + fixmeObject(None, cat, format, *args) + + +def info(cat, format, *args): + infoObject(None, cat, format, *args) + + +def debug(cat, format, *args): + debugObject(None, cat, format, *args) + + +def log(cat, format, *args): + logObject(None, cat, format, *args) + +# public utility functions + + +def getExceptionMessage(exception, frame=-1, filename=None): + """Returns a short message based on an exception. + + Useful for debugging. + Tries to find where the exception was triggered. + """ + stack = traceback.extract_tb(sys.exc_info()[2]) + if filename: + stack = [f for f in stack if f[0].find(filename) > -1] + # import code; code.interact(local=locals()) + (filename, line, func, text) = stack[frame] + filename = scrubFilename(filename) + exc = exception.__class__.__name__ + msg = "" + # a shortcut to extract a useful message out of most exceptions + # for now + if str(exception): + msg = ": %s" % str(exception) + return "exception %(exc)s at %(filename)s:%(line)s: %(func)s()%(msg)s" \ + % locals() + + +def reopenOutputFiles(): + """Reopens the stdout and stderr output files, as set by `outputToFiles`.""" + if not _stdout and not _stderr: + debug('log', 'told to reopen log files, but log files not set') + return + + def reopen(name, fileno, *args): + oldmask = os.umask(0o026) + try: + f = open(name, 'a+', *args) + finally: + os.umask(oldmask) + + os.dup2(f.fileno(), fileno) + + if _stdout: + reopen(_stdout, sys.stdout.fileno()) + + if _stderr: + reopen(_stderr, sys.stderr.fileno(), 0) + debug('log', 'opened log %r', _stderr) + + +def outputToFiles(stdout=None, stderr=None): + """Redirects stdout and stderr to the specified files. + + Records the file names so that a future call to reopenOutputFiles() + can open the same files. Installs a SIGHUP handler that will reopen + the output files. + + Note that stderr is opened unbuffered, so if it shares a file with + stdout then interleaved output may not appear in the order that you + expect. + """ + global _stdout, _stderr, _old_hup_handler + _stdout, _stderr = stdout, stderr + reopenOutputFiles() + + def sighup(signum, frame): + info('log', "Received SIGHUP, reopening logs") + reopenOutputFiles() + if _old_hup_handler: + info('log', "Calling old SIGHUP hander") + _old_hup_handler(signum, frame) + + debug('log', 'installing SIGHUP handler') + from . import signal + handler = signal.signal(signal.SIGHUP, sighup) + if handler == signal.SIG_DFL or handler == signal.SIG_IGN: + _old_hup_handler = None + else: + _old_hup_handler = handler + + +# base class for loggable objects + + +class BaseLoggable(object): + """Base class for objects that want to be able to log messages. + + The levels of severity for the messages are, in order from least + to most: log, debug, info, warning, error. + + Attributes: + logCategory (str): The category under which the messages will be filed. + Can be used to set a display filter. + """ + + def error(self, *args): + """Logs an error. + + By default this will also raise an exception. + """ + if _canShortcutLogging(self.logCategory, ERROR): + return + errorObject(self.logObjectName(), + self.logCategory, *self.logFunction(*args)) + + def warning(self, *args): + """Logs a warning. + + Used for non-fatal problems. + """ + if _canShortcutLogging(self.logCategory, WARN): + return + warningObject( + self.logObjectName(), self.logCategory, *self.logFunction(*args)) + + def fixme(self, *args): + """Logs a fixme. + + Used for FIXMEs. + """ + if _canShortcutLogging(self.logCategory, FIXME): + return + fixmeObject(self.logObjectName(), + self.logCategory, *self.logFunction(*args)) + + def info(self, *args): + """Logs an informational message. + + Used for normal operation. + """ + if _canShortcutLogging(self.logCategory, INFO): + return + infoObject(self.logObjectName(), + self.logCategory, *self.logFunction(*args)) + + def debug(self, *args): + """Logs a debug message. + + Used for debugging. + """ + if _canShortcutLogging(self.logCategory, DEBUG): + return + debugObject(self.logObjectName(), + self.logCategory, *self.logFunction(*args)) + + def log(self, *args): + """Logs a log message. + + Used for debugging recurring events. + """ + if _canShortcutLogging(self.logCategory, LOG): + return + logObject(self.logObjectName(), + self.logCategory, *self.logFunction(*args)) + + def doLog(self, level, where, format, *args, **kwargs): + """Logs a message at the specified level, with the possibility of going + higher up in the stack. + + Args: + level (int): The log level. + where (int or function): How many frames to go back from + the last log frame, must be negative; or a function + (to log for a future call). + format (str): The string template for the message. + *args: The arguments used when converting the `format` + string template to the message. + **kwargs: The pre-calculated values from a previous doLog call. + + Returns: + dict: The calculated variables, to be reused in a + call to doLog that should show the same location. + """ + if _canShortcutLogging(self.logCategory, level): + return {} + args = self.logFunction(*args) + return doLog(level, self.logObjectName(), self.logCategory, + format, args, where=where, **kwargs) + + def logFunction(self, *args): + """Processes the arguments applied to the message template. + + Default just returns the arguments unchanged. + """ + return args + + def logObjectName(self): + """Gets the name of this object.""" + # cheat pychecker + for name in ['logName', 'name']: + if hasattr(self, name): + return getattr(self, name) + + return None + + def handleException(self, exc): + self.warning(getExceptionMessage(exc)) + + +class Loggable(BaseLoggable): + + def __init__(self, logCategory=None): + if logCategory: + self.logCategory = logCategory + elif not hasattr(self, 'logCategory'): + self.logCategory = self.__class__.__name__.lower() + + def logObjectName(self): + res = BaseLoggable.logObjectName(self) + if not res: + return "<%s at 0x%x>" % (self.__class__.__name__, id(self)) + return res + + def error(self, format, *args): + if _canShortcutLogging(self.logCategory, ERROR): + return + doLog(ERROR, self.logObjectName(), self.logCategory, + format, self.logFunction(*args), where=-2) diff --git a/validate/launcher/main.py b/validate/launcher/main.py new file mode 100644 index 0000000000..745a7740d1 --- /dev/null +++ b/validate/launcher/main.py @@ -0,0 +1,686 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2014,Thibault Saunier +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. +import os +import sys +import shutil +from . import utils +import urllib.parse +from . import loggable +import multiprocessing +import argparse +import tempfile +from . import reporters +import subprocess + + +from .loggable import Loggable +from .baseclasses import _TestsLauncher, ScenarioManager +from .utils import printc, path2url, DEFAULT_MAIN_DIR, launch_command, Colors, Protocols, which + + +LESS = "less" +HELP = ''' + +=============================================================================== + gst-validate-launcher +=============================================================================== + +1. Introduction +---------------- + +gst-validate-launcher is a test launcher tool. It has been designed to +launch the various tools included in GstValidate, running tests on real +media files. This means that with gst-validate-launcher, you can launch +many tests automatically in one simple command. It then permits to +aggregate results and print them in a human readable way on stdout +and serializing them in the following implemented formats: + + * %s + +We support all the tools provided in GstValidate in the launcher, but +we also support ges-launch when the GStreamer Editing Services have +been compiled against GstValidate. + +2. Default test suite +--------------------- + +A default suite of tests is provided and is available at: http://gitlab.freedesktop.org/gstreamer/gst-integration-testsuites/ +You can run it pretty simply doing: + +. $gst-validate-launcher --sync + +That will download Gstreamer upstream default assets into the +default folder (%s) and run all currently +activated tests. Note that we use git-annex https://git-annex.branchable.com/ so +you will need that tool to get started. + +3. Implement your own tests +--------------------------- + +To implement new tests, you will just need to set the media path using the +--medias-paths argument. If you want to run all available scenarios on all the +file present in that folder, you should run the first time: + +. $gst-validate-launcher --medias-paths /path/to/media/files --generate-media-info + +That will generate the .media_info files that contains information about the media +files present in that folder. Those media_info files are simple XML file describing +the topology of the media files. You need not reuse --generate-media-info from +next time. The generated media files will be used as a reference for following +runs. You might want to check that they contain the right information yourself +the first time. + +Once .media-info is generated, you can update it using --update-media-info. + +Those .media_info are the files that are used by gst-validate-launcher to know +what media files can be used for the different scenarios. For example if a +file is not seekable, seeking scenarios will not be run on it etc... + +3.1 Scenarios specific to a media file/stream: +---------------------------------------------- + +It is possible that some scenarios are very specific to one media file. In that case, +the .scenario file should be present in the same folder as the .media_info file and +be called similarly. For example for a file called /some/media/file.mp4, the media_info +file will be called /some/media/file.media_info and a scenario that will seek to a position that +is known to fail would be called: /some/media/file.mp4.seek_to_failing_pos.scenario and +gst-validate-launcher will run that scenario only on that media file. + +3.2 Test media accessible through other protocols: +-------------------------------------------------- + +Currently gst-validate-launcher supports the following protocols: + + * %s + +It does not mean you can not test other protocols but it means that it has not been +properly tested. + +To test medias that use those protocols, you should simply make sure that there +is a media descriptor file with .stream_info as an extension in your --media-paths. +You can generate such a file doing: + +. $gst-validate-media-check-1.0 http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8 --output-file /somewhere/in/you/media/path/bipbop.stream_info + +Once this is done, gst-validate-launcher will run the scenarios on those media files the +same way as if they were local files. + + +4. Debug gst-validate-launcher execution +---------------------------------------- + +You can activate debug logs setting the environment variable GST_VALIDATE_LAUNCHER_DEBUG. + +. $GST_VALIDATE_LAUNCHER_DEBUG=6 gst-validate-launcher + +It uses the same syntax as PITIVI_DEBUG (more information at: +https://developer.pitivi.org/Bug_reporting.html#debug-logs). +''' % ("\n * ".join([reporter.name for reporter in + utils.get_subclasses(reporters.Reporter, reporters.__dict__)] + ), + DEFAULT_MAIN_DIR, + "\n * ".join([getattr(Protocols, att) for att in + dir(Protocols) if isinstance(getattr(Protocols, att), str) and not + att.startswith("_")])) + +if "--help" not in sys.argv: + HELP = "Use --help for the full help" + +QA_ASSETS = "gst-integration-testsuites" +MEDIAS_FOLDER = "medias" +DEFAULT_GST_QA_ASSETS_REPO = "https://gitlab.freedesktop.org/gstreamer/gst-integration-testsuites.git" + + +def download_assets(options): + try: + printc("About to download assets from %s to %s" % (options.remote_assets_url, + options.clone_dir)) + launch_command("%s %s %s" % (options.get_assets_command, + options.remote_assets_url, + options.clone_dir), + fails=True) + except subprocess.CalledProcessError as e: + if "git" in options.get_assets_command: + m = "\n\nMAKE SURE YOU HAVE git INSTALLED!" + else: + m = "" + + printc("Could not download assets\n\nError: %s%s" % (e, m), + Colors.FAIL, True) + + return False + + return True + + +class PrintUsage(argparse.Action): + + def __init__(self, option_strings, dest=argparse.SUPPRESS, default=argparse.SUPPRESS, help=None): + super( + PrintUsage, self).__init__(option_strings=option_strings, dest=dest, + default=default, nargs=0, help=help) + + def __call__(self, parser, namespace, values, option_string=None): + print(HELP) + parser.exit() + + +def _positive_integer_type(value): + cast = int(value) + if cast <= 0: + raise argparse.ArgumentTypeError(f'`{value}\' is not a positive integer') + return cast + + +class LauncherConfig(Loggable): + + def __init__(self): + self.testsuites = [] + self.debug = False + self.forever = False + self.n_runs = None + self.fatal_error = False + self.wanted_tests = [] + self.blacklisted_tests = [] + self.list_tests = False + self.mute = True + self.unmute = not self.mute + self.no_color = False + self.generate_info = False + self.update_media_info = False + self.generate_info_full = False + self.long_limit = utils.LONG_TEST + self.config = None + self.valgrind = False + self.gdb = False + self.no_display = False + self.rr = False + self.xunit_file = None + self.main_dir = utils.DEFAULT_MAIN_DIR + self.output_dir = None + self.logsdir = None + self.privatedir = None + self.redirect_logs = False + self.num_jobs = max(multiprocessing.cpu_count(), 1) + self.dest = None + self._using_default_paths = False + # paths passed with --media-path, and not defined by a testsuite + self.user_paths = [] + self.paths = [] + self.testsuites_dirs = utils.DEFAULT_TESTSUITES_DIRS + + self.clone_dir = None + + self.http_server_port = 8079 + self.http_bandwith = 1024 * 1024 + self.http_server_dir = None + self.httponly = False + self.get_assets_command = "git clone" + self.remote_assets_url = DEFAULT_GST_QA_ASSETS_REPO + self.sync = False + self.force_sync = False + self.sync_all = False + self.sync_version = None + self.check_bugs_status = False + self.retry_on_failures = False + self.html = False + + def cleanup(self): + """ + Cleanup the options looking after user options have been parsed + """ + + # Get absolute path for main_dir and base everything on that + self.main_dir = os.path.abspath(self.main_dir) + os.makedirs(self.main_dir, exist_ok=True) + os.environ['GST_VALIDATE_LAUNCHER_MAIN_DIR'] = self.main_dir + + # default for output_dir is MAINDIR + if not self.output_dir: + self.output_dir = self.main_dir + else: + self.output_dir = os.path.abspath(self.output_dir) + + self.mute = not self.unmute + if self.gdb_non_stop: + self.gdb = True + + if self.gdb: + self.logsdir = "stdout" + self.debug = True + self.num_jobs = 1 + try: + subprocess.check_output("gdb --help", shell=True) + except subprocess.CalledProcessError: + printc("Want to use gdb, but not available on the system", + Colors.FAIL) + return False + + # other output directories + if self.logsdir in ['stdout', 'stderr']: + # Allow -l stdout/stderr to work like -rl stdout/stderr + self.redirect_logs = self.logsdir + self.logsdir = None + if self.verbose: + self.redirect_logs = 'stdout' + self.logsdir = None + if self.logsdir is None: + self.logsdir = os.path.join(self.output_dir, "logs") + if self.dest is None: + self.dest = os.path.join(self.output_dir, "rendered") + self.privatedir = os.path.join(self.output_dir, "launcher-private") + + destparsed = urllib.parse.urlparse(self.dest) + if destparsed.scheme == "" or destparsed.scheme == "file": + os.makedirs(destparsed.path, exist_ok=True) + if destparsed.scheme == "": + self.dest = path2url(self.dest) + if not os.path.exists(self.logsdir): + os.makedirs(self.logsdir) + if not os.path.exists(self.privatedir): + os.makedirs(self.privatedir) + + if self.redirect_logs not in ['stdout', 'stderr', False]: + printc("Log redirection (%s) must be either 'stdout' or 'stderr'." + % self.redirect_logs, Colors.FAIL, True) + return False + + if self.no_color: + utils.desactivate_colors() + if self.clone_dir is None: + if not utils.USING_SUBPROJECT: + self.clone_dir = os.path.join(self.main_dir, QA_ASSETS) + else: + self.clone_dir = self.main_dir + + if not isinstance(self.paths, list): + self.paths = [self.paths] + + if not isinstance(self.user_paths, list): + self.user_paths = [self.user_paths] + + self.paths = list(set(self.paths).union(set(self.user_paths))) + + if self.generate_info_full is True: + self.generate_info = True + + if not utils.USING_SUBPROJECT: + if self.sync_all is True or self.force_sync is True: + self.sync = True + + if not self.sync and not os.path.exists(self.clone_dir) and \ + self.clone_dir == os.path.join(self.clone_dir, MEDIAS_FOLDER): + printc("Media path (%s) does not exists. Forgot to run --sync ?" + % self.clone_dir, Colors.FAIL, True) + return False + + if (self.main_dir != DEFAULT_MAIN_DIR or self.clone_dir != QA_ASSETS): + local_clone_dir = os.path.join( + self.main_dir, self.clone_dir, "testsuites") + if local_clone_dir not in self.testsuites_dirs: + self.testsuites_dirs.insert(0, local_clone_dir) + if self.valgrind: + try: + subprocess.check_output("valgrind --help", shell=True) + except subprocess.CalledProcessError: + printc("Want to use valgrind, but not available on the system", + Colors.FAIL) + return False + + if self.rr: + if not shutil.which('rr'): + printc("Want to use rr, but not available on the system", + Colors.FAIL) + return False + + if self.html: + try: + import commonmark + except ImportError: + printc("You want to output html logs but commonmark not found. Install it" + " with `pip install commonmark` and try again.", Colors.FAIL) + return False + + return True + + def set_http_server_dir(self, path): + if self.http_server_dir is not None: + printc("Server directory already set to %s" % self.http_server_dir) + return + + self.http_server_dir = path + + def add_paths(self, paths, force=False): + if force is False: + if self.paths: + return + if not isinstance(paths, list): + paths = [paths] + + if self._using_default_paths: + self.paths = paths + self._using_default_paths = False + else: + for path in paths: + if path not in self.paths: + self.paths.append(path) + + @staticmethod + def create_parser(): + parser = argparse.ArgumentParser( + formatter_class=argparse.RawTextHelpFormatter, + prog='gst-validate-launcher', description=HELP) + + parser.add_argument('testsuites', metavar='N', nargs='*', + help="""Lets you specify a test to run, a testsuite name or a file where the testsuite to execute is defined. + + In the module if you want to work with a specific test manager(s) (for example, + 'ges' or 'validate'), you should define the TEST_MANAGER variable in the + testsuite file (it can be a list of test manager names) + + In this file you should implement a setup_tests function. That function takes + a TestManager and the GstValidateLauncher option as parameters and return True + if it succeeded loading the tests, False otherwise. + You will be able to configure the TestManager with its various methods. This + function will be called with each TestManager usable, for example you will be + passed the 'validate' TestManager in case the GstValidateManager launcher is + available. You should configure it using: + + * test_manager.add_scenarios: which allows you to register a list of scenario names to be run + * test_manager.set_default_blacklist: Lets you set a list of tuple of the form: + (@regex_defining_blacklister_test_names, @reason_for_the_blacklisting) + * test_manager.add_generators: which allows you to register a list of #GstValidateTestsGenerator + to be used to generate tests + * test_manager.add_encoding_formats:: which allows you to register a list #MediaFormatCombination to be used for transcoding tests + + You can also set default values with: + * test_manager.register_defaults: Sets default values for all parametters + * test_manager.register_default_test_generators: Sets default values for the TestsGenerators to be used + * test_manager.register_default_scenarios: Sets default values for the scenarios to be executed + * test_manager.register_default_encoding_formats: Sets default values for the encoding formats to be tested + + Note that all testsuite should be inside python modules, so the directory should contain a __init__.py file + """, + default=["validate"]) + parser.add_argument("-d", "--debug", dest="debug", + action="store_true", + help="Let user debug the process on timeout") + parser.add_argument("--timeout-factor", dest="timeout_factor", + default=1.0, type=float, + help="Factor to be applied on all timeout values.") + parser.add_argument("-f", "--forever", dest="forever", + action="store_true", + help="Keep running tests until one fails") + parser.add_argument("--n-runs", dest="n_runs", action='store', + help="Number of runs, if the testsuites." + " Meaning no failure will stop the testuite" + " run meanwhile.", type=int), + parser.add_argument("-F", "--fatal-error", dest="fatal_error", + action="store_true", + help="Stop on first fail") + parser.add_argument("--fail-on-testlist-change", + dest="fail_on_testlist_change", + action="store_true", + help="Fail the testsuite if a test has been added" + " or removed without being explicitely added/removed " + "from the testlist file.") + parser.add_argument("-t", "--wanted-tests", dest="wanted_tests", + action="append", + help="Define the tests to execute, it can be a regex." + " If it contains defaults_only, only default scenarios" + " will be executed") + parser.add_argument("-b", "--blacklisted-tests", dest="blacklisted_tests", + action="append", + help="Define the tests not to execute, it can be a regex.") + parser.add_argument("--check-bugs", dest="check_bugs_status", + action="store_true", + help="Check if the bug linked to blacklisted tests has" + " been marked as resolved. (works with gitlab and bugzilla)") + parser.add_argument("-L", "--list-tests", + dest="list_tests", + action="store_true", + help="List tests and exit") + parser.add_argument("--unmute", dest="unmute", + action="store_true", + help="Unmute playback output, which means that we use " + "'real' sinks") + parser.add_argument("-m", "--mute", dest="mute", + action="store_true", + help="Mute playback output, which means that we use " + "a fakesink") + parser.add_argument("-n", "--no-color", dest="no_color", + action="store_true", + help="Set it to output no colored text in the terminal") + parser.add_argument("-g", "--generate-media-info", dest="generate_info", + action="store_true", + help="Set it in order to generate the missing .media_infos files") + parser.add_argument("--update-media-info", dest="update_media_info", + action="store_true", + help="Set it in order to update existing .media_infos files") + parser.add_argument( + "-G", "--generate-media-info-with-frame-detection", dest="generate_info_full", + action="store_true", + help="Set it in order to generate the missing .media_infos files. " + "It implies --generate-media-info but enabling frame detection") + parser.add_argument("-lt", "--long-test-limit", dest="long_limit", + action='store', + help="Defines the limit for which a test is considered as long (in seconds)." + " Note that 0 will enable all tests", type=int), + parser.add_argument("--dump-on-failure", dest="dump_on_failure", + action="store_true", default=False, + help="Dump logs to stdout when a test fails." + " Note that bat is used to enhance output if available" + " (See https://github.com/sharkdp/bat)") + parser.add_argument("--max-dump-size", dest="max_dump_size", type=float, + default=0.5, help="Maximum size of logs to dump on stdout in MB.") + parser.add_argument("-c", "--config", dest="config", + help="This is DEPRECATED, prefer using the testsuite format" + " to configure testsuites") + parser.add_argument("-vg", "--valgrind", dest="valgrind", + action="store_true", + help="Run the tests inside Valgrind") + parser.add_argument("-rr", "--rr", dest="rr", + action="store_true", + help="Run the tests inside rr record") + parser.add_argument("--gdb", dest="gdb", + action="store_true", + help="Run the tests inside gdb (implies" + " --output-dir=stdout and --jobs=1)") + parser.add_argument("--gdb-non-stop", dest="gdb_non_stop", + action="store_true", + help="Run the test automatically in gdb (implies --gdb)") + parser.add_argument("-nd", "--no-display", dest="no_display", + action="store_true", + help="Run the tests without outputting graphics" + " on any display. It tries to run all graphical operation" + " in a virtual framebuffer." + " Note that it is currently implemented only" + " for the X server thanks to Xvfb (which is requeried in that case)") + parser.add_argument('--xunit-file', dest='xunit_file', + action='store', metavar="FILE", + help=("Path to xml file to store the xunit report in.")) + parser.add_argument('--shuffle', dest="shuffle", action="store_true", + help="Runs the test in a random order. Can help speed up the overall" + " test time by running synchronized and unsynchronized tests" + " at the same time") + parser.add_argument('--retry-on-failures', dest="retry_on_failures", action="store_true", + help="Re-try tests that produce unexpected results") + parser.add_argument('--html', dest="html", action="store_true", + help="Write logs as html") + dir_group = parser.add_argument_group( + "Directories and files to be used by the launcher") + dir_group.add_argument("-M", "--main-dir", dest="main_dir", + help="Main directory where to put files." + " Respects the GST_VALIDATE_LAUNCHER_MAIN_DIR environment variable." + " Default is %s" % DEFAULT_MAIN_DIR) + dir_group.add_argument("--testsuites-dir", dest="testsuites_dirs", action='append', + help="Directory where to look for testsuites. Default is %s" + % utils.DEFAULT_TESTSUITES_DIRS) + dir_group.add_argument("-o", "--output-dir", dest="output_dir", + help="Directory where to store logs and rendered files. Default is MAIN_DIR") + dir_group.add_argument("-l", "--logs-dir", dest="logsdir", + help="Directory where to store logs, default is OUTPUT_DIR/logs.") + dir_group.add_argument("-R", "--render-path", dest="dest", + help="Set the path to which projects should be rendered, default is OUTPUT_DIR/rendered") + dir_group.add_argument("-p", "--medias-paths", dest="user_paths", action="append", + help="Paths in which to look for media files") + dir_group.add_argument("-a", "--clone-dir", dest="clone_dir", + help="Paths where to clone the testuite to run." + " default is MAIN_DIR/gst-integration-testsuites") + dir_group.add_argument("-rl", "--redirect-logs", dest="redirect_logs", + help="Redirect logs to 'stdout' or 'sdterr'.") + dir_group.add_argument("-v", "--verbose", dest="verbose", + action='count', + help="Redirect logs to stdout.") + dir_group.add_argument("-j", "--jobs", dest="num_jobs", + help="Number of tests to execute simultaneously" + " (Defaults to the number of cores of the processor)", + type=_positive_integer_type) + dir_group.add_argument("--ignore-numfailures", dest="ignore_numfailures", + help="Ignore the number of failed test in exit code", + default=False, action='store_true') + dir_group.add_argument("--parts", dest="num_parts", + help="Splits the tests in equally distributed parts and only run one part" + " (Defaults to 1 part)", + type=int, default=1) + dir_group.add_argument("--part-index", dest="part_index", + help="The index of the part to be run (starts at 1).", + type=int, default=1) + + http_server_group = parser.add_argument_group( + "Handle the HTTP server to be created") + http_server_group.add_argument( + "--http-server-port", dest="http_server_port", + help="Port on which to run the http server on localhost", type=int) + http_server_group.add_argument( + "--http-bandwith-limitation", dest="http_bandwith", + help="The artificial bandwith limitation to introduce to the local server (in Bytes/sec) (default: 1 MBps)") + http_server_group.add_argument( + "-s", "--folder-for-http-server", dest="http_server_dir", + help="Folder in which to create an http server on localhost. Default is PATHS") + http_server_group.add_argument("--http-only", dest="httponly", + action='store_true', + help="Start the http server and quit") + + assets_group = parser.add_argument_group("Handle remote assets") + assets_group.add_argument( + "--get-assets-command", dest="get_assets_command", + help="Command to get assets") + assets_group.add_argument("--remote-assets-url", dest="remote_assets_url", + help="Url to the remote assets (default:%s)" % DEFAULT_GST_QA_ASSETS_REPO) + assets_group.add_argument("-S", "--sync", dest="sync", action="store_true", + help="Synchronize asset repository") + assets_group.add_argument("-fs", "--force-sync", dest="force_sync", action="store_true", + help="Synchronize asset repository reseting any change that might have" + " happened in the testsuite") + assets_group.add_argument("--sync-all", dest="sync_all", action="store_true", + help="Synchronize asset repository," + " including big media files") + assets_group.add_argument("--sync-version", dest="sync_version", + help="Version of the asset repository, default is GST_VALIDATE_TESTSUITE_VERSION") + assets_group.add_argument("--usage", action=PrintUsage, + help="Print usage documentation") + return parser + + +def setup_launcher_from_args(args, main_options=None): + loggable.init("GST_VALIDATE_LAUNCHER_DEBUG", True, False) + parser = LauncherConfig.create_parser() + tests_launcher = _TestsLauncher() + tests_launcher.add_options(parser) + + if "--help" in sys.argv and which(LESS): + tmpf = tempfile.NamedTemporaryFile(mode='r+') + + parser.print_help(file=tmpf) + os.system("%s %s" % (LESS, tmpf.name)) + return False, None, None + + options = LauncherConfig() + parser.parse_args(args=args, namespace=options) + if main_options: + # Override output directories and logging properties of the sub launcher. + for option in ["main_dir", "output_dir", "logsdir", "dest", "clone_dir", + "redirect_logs", "verbose", "timeout_factor"]: + setattr(options, option, getattr(main_options, option)) + if not options.cleanup(): + return False, None, None + + if options.remote_assets_url and options.sync and not os.path.exists(options.clone_dir): + if not download_assets(options): + return False, None, None + + # Ensure that the scenario manager singleton is ready to be used + ScenarioManager().config = options + if not tests_launcher.set_settings(options, []): + return False, None, None + + return True, options, tests_launcher + + +def main(libsdir): + global LIBSDIR + LIBSDIR = libsdir + + utils.DEFAULT_TESTSUITES_DIRS.append(os.path.join(LIBSDIR, "testsuites")) + os.environ["GST_VALIDATE_APPS_DIR"] = os.path.join( + LIBSDIR, "apps") + os.pathsep + os.environ.get("GST_VALIDATE_APPS_DIR", "") + + res, options, tests_launcher = setup_launcher_from_args(sys.argv[1:]) + if res is False: + return 1 + + if options.list_tests: + if tests_launcher.list_tests() == -1: + printc("\nFailling as tests have been removed/added " + " (--fail-on-testlist-change)", Colors.FAIL) + return 1 + + tests = tests_launcher.tests + for test in tests: + printc(test) + + printc("\nNumber of tests: %d" % len(tests), Colors.OKGREEN) + return 0 + + if options.httponly is True: + print("Running HTTP server only") + return 0 + + # There seems to be some issue with forking, dconf and some gtype + # initialization that deadlocks occasionally, setting the + # GSettings backend make it go away. + # Also happened here: + # https://cgit.freedesktop.org/gstreamer/gst-plugins-good/commit/tests/check/Makefile.am?id=8e2c1d1de56bddbff22170f8b17473882e0e63f9 + os.environ['GSETTINGS_BACKEND'] = "memory" + + exception = None + try: + tests_launcher.run_tests() + except Exception as e: + exception = e + pass + finally: + res = tests_launcher.final_report() + if options.ignore_numfailures: + res = 0 + if exception is not None: + raise exception + + return res diff --git a/validate/launcher/meson.build b/validate/launcher/meson.build new file mode 100644 index 0000000000..b767956868 --- /dev/null +++ b/validate/launcher/meson.build @@ -0,0 +1,29 @@ +_launcherdir = get_option('libdir') + '/gst-validate-launcher/python/launcher/' + +launcher_configure = configuration_data() +launcher_configure.set('GST_VALIDATE_TESTSUITE_VERSION', '@0@'.format(TESTSUITE_VERSION)) +launcher_configure.set('BUILDDIR', meson.build_root()) +launcher_configure.set('SRCDIR', meson.source_root()) +launcher_configure.set('DATADIR', join_paths(get_option('prefix'), get_option('datadir'))) +launcher_configure.set('LIBDIR', join_paths(get_option('prefix'), get_option('libdir'))) + +configure_file(input : 'config.py.in', + output : 'config.py', + install_dir: _launcherdir, + configuration : launcher_configure) + +_sources = ['baseclasses.py', + '__init__.py', + 'loggable.py', + 'reporters.py', + 'main.py', + 'httpserver.py', + 'RangeHTTPServer.py', + 'utils.py', + 'vfb_server.py'] + +install_data(sources: _sources, + install_dir: _launcherdir) + +subdir('apps') +subdir('testsuites') diff --git a/validate/launcher/reporters.py b/validate/launcher/reporters.py new file mode 100644 index 0000000000..3f100bd695 --- /dev/null +++ b/validate/launcher/reporters.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2013,Thibault Saunier +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +""" Test Reporters implementation. """ + +import os +import re +import time +import codecs +import datetime +import tempfile +from .loggable import Loggable +from xml.sax import saxutils +from .utils import Result, printc, Colors + +UNICODE_STRINGS = (type(str()) == type(str())) # noqa + + +class UnknownResult(Exception): + pass + + +CONTROL_CHARACTERS = re.compile(r"[\000-\010\013\014\016-\037]") + + +def xml_safe(value): + """Replaces invalid XML characters with '?'.""" + return CONTROL_CHARACTERS.sub('?', value) + + +def escape_cdata(cdata): + """Escape a string for an XML CDATA section.""" + return xml_safe(cdata).replace(']]>', ']]>]]> 0: + printc("\n%sTotal time spent: %s seconds\n" % + ((lenstat * " "), datetime.timedelta( + seconds=(time.time() - self._start_time))), + Colors.OKBLUE) + printc("%sPassed: %d" % + (lenstat * " ", self.stats["passed"]), Colors.OKGREEN) + printc("%sSkipped: %d" % + (lenstat * " ", self.stats["skipped"]), Colors.WARNING) + printc("%sFailed: %d" % + (lenstat * " ", self.stats["failures"]), Colors.FAIL) + printc("%sKnown error: %d" % + (lenstat * " ", self.stats["known_error"]), Colors.OKBLUE) + printc("%s%s" % + (lenstat * " ", (len("Failed: 0")) * "-"), Colors.OKBLUE) + + total = self.stats["failures"] + self.stats["passed"] + color = Colors.WARNING + if total == self.stats["passed"]: + color = Colors.OKGREEN + elif total == self.stats["failures"]: + color = Colors.FAIL + + printc("%sTotal: %d" % (lenstat * " ", total), color) + + return self.stats["failures"] + + +class XunitReporter(Reporter): + + """This reporter provides test results in the standard XUnit XML format.""" + name = 'xunit' + encoding = 'UTF-8' + + def __init__(self, options): + super(XunitReporter, self).__init__(options) + + self._createTmpFile() + + def final_report(self): + self.report() + return super(XunitReporter, self).final_report() + + def _get_all_logs_data(self, test): + if not self.options.redirect_logs: + return "" + + captured = "" + value = test.get_log_content() + if value: + captured += escape_cdata(value) + for extralog in test.extra_logfiles: + captured += "\n\n===== %s =====\n\n" % escape_cdata( + os.path.basename(extralog)) + value = test.get_extra_log_content(extralog) + captured += escape_cdata(value) + + return captured + + def _get_captured(self, test): + return '' % self._get_all_logs_data(test) + + def _quoteattr(self, attr): + """Escape an XML attribute. Value can be unicode.""" + attr = xml_safe(attr) + if isinstance(attr, str) and not UNICODE_STRINGS: + attr = attr.encode(self.encoding) + return saxutils.quoteattr(attr) + + def report(self): + """Writes an Xunit-formatted XML file + + The file includes a report of test errors and failures. + + """ + self.debug("Writing XML file to: %s", self.options.xunit_file) + xml_file = codecs.open(self.options.xunit_file, 'w', + self.encoding, 'replace') + + self.stats['encoding'] = self.encoding + self.stats['total'] = (self.stats['timeout'] + self.stats['failures'] + + self.stats['passed'] + self.stats['skipped']) + + xml_file.write('' + '' % self.stats) + + tmp_xml_file = codecs.open(self.tmp_xml_file.name, 'r', + self.encoding, 'replace') + + for l in tmp_xml_file: + xml_file.write(l) + + xml_file.write('') + xml_file.close() + tmp_xml_file.close() + os.remove(self.tmp_xml_file.name) + + self._createTmpFile() + + def _createTmpFile(self): + self.tmp_xml_file = tempfile.NamedTemporaryFile(delete=False) + self.tmp_xml_file.close() + + def set_failed(self, test): + """Add failure output to Xunit report. + """ + super().set_failed(test) + + xml_file = codecs.open(self.tmp_xml_file.name, 'a', + self.encoding, 'replace') + xml_file.write(self._forceUnicode( + '' + '%(logs)s' + '' % + {'name': self._quoteattr(test.get_classname() + '.' + test.get_name()), + 'taken': test.time_taken, + 'logs': self._get_all_logs_data(test), + 'errtype': self._quoteattr(test.result), + 'message': self._quoteattr(test.message), + })) + xml_file.close() + + def set_passed(self, test): + """Add success output to Xunit report. + """ + self.stats['passed'] += 1 + + xml_file = codecs.open(self.tmp_xml_file.name, 'a', + self.encoding, 'replace') + xml_file.write(self._forceUnicode( + '%(systemout)s' % + {'name': self._quoteattr(test.get_classname() + '.' + test.get_name()), + 'taken': test.time_taken, + 'systemout': self._get_captured(test), + })) + xml_file.close() + + def _forceUnicode(self, s): + if not UNICODE_STRINGS: + if isinstance(s, str): + s = s.decode(self.encoding, 'replace') + return s diff --git a/validate/launcher/testsuites/check.py b/validate/launcher/testsuites/check.py new file mode 100644 index 0000000000..89839167c0 --- /dev/null +++ b/validate/launcher/testsuites/check.py @@ -0,0 +1,173 @@ +# -*- Mode: Python -*- vi:si:et:sw=4:sts=4:ts=4:syntax=python +# +# Copyright (c) 2016,Thibault Saunier +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +""" +GStreamer unit tests +""" +from launcher import utils + +TEST_MANAGER = "check" + +KNOWN_NOT_LEAKY = r'^check.gst-devtools.*|^check.gstreamer.*|^check-gst-plugins-base|^check.gst-plugins-ugly|^check.gst-plugins-good' + +# These tests take very long compared to what they add, so let's skip them. +LONG_VALGRIND_TESTS = [ + (r'check.[a-z-]*.generic_states.test_state_changes_down_seq', 'enough to run one of the sequences'), + (r'check.[a-z-]*.generic_states.test_state_changes_up_seq', 'enough to run one of the sequences',), + (r'check.gstreamer.gst_gstelement.test_foreach_pad$', '48s'), + (r'check.gstreamer.gst_gstinfo.info_post_gst_init_category_registration$', '21s'), + (r'check.gstreamer.gst_gstsystemclock.test_resolution$', '60s'), + (r'check.gstreamer.libs_aggregator.test_infinite_seek$', '20s'), + (r'check.gstreamer.libs_aggregator.test_infinite_seek_50_src$', '20s'), + (r'check.gstreamer.libs_gstharness.test_harness_element_ref$', '20s'), + (r'check.gstreamer.pipelines_simple_launch_lines.test_2_elements$', '58s'), + (r'check.gstreamer.pipelines_stress.test_stress$', '54s'), + (r'check.gstreamer.pipelines_stress.test_stress_preroll$', '27s'), + (r'check.gst-plugins-base.elements_appsrc.test_appsrc_block_deadlock$', '265.595s'), + (r'check.gst-plugins-base.elements_audioresample.test_fft$', '91.247s'), + (r'check.gst-plugins-base.elements_audioresample.test_timestamp_drift$', '141.784s'), + (r'check.gst-plugins-base.elements-videoscale.*', 'superlong'), + (r'check.gst-plugins-base.libs_video.test_overlay_blend$', '74.096s'), + (r'check.gst-plugins-base.libs_video.test_video_color_convert$', '345.271s'), + (r'check.gst-plugins-base.libs_video.test_video_formats$', '70.987s'), + (r'check.gst-plugins-base.libs_video.test_video_size_convert$', '56.387s'), + (r'check.gst-plugins-base.elements_audiointerleave.test_audiointerleave_2ch_pipeline_$', '5 *51.069s'), + (r'check.gst-plugins-base.elements_multifdsink.test_client_kick$', '46.909s'), + (r'check.gst-plugins-base.elements_videotestsrc.test_all_patterns$', '?'), + (r'check.gst-plugins-base.elements_videotestsrc.test_patterns_are_deterministic$', '?'), + (r'check.gst-plugins-good.elements_shapewipe.test_general$', '325s'), + (r'check.gst-plugins-good.elements_videocrop.test_cropping$', '245s'), + (r'check.gst-plugins-good.elements_videomixer.*', '30s (alsodeprecated)'), + (r'check.gst-plugins-good.elements_rtp_payloading.rtp_jpeg_packet_loss$', '109s'), + (r'check.gst-plugins-good.elements_videomixer.test_play_twice_then_add_and_play_again$', '55s'), + (r'check.gst-plugins-good.pipelines_effectv.test_quarktv$', '53s'), + (r'check.gst-plugins-good.elements_deinterlace.test_mode_disabled_passthrough$', '52s'), + (r'check.gst-plugins-good.elements_deinterlace.test_mode_auto_deinterlaced_passthrough$', '28s'), + (r'check.gst-plugins-good.elements_deinterleave.test_2_channels_caps_change$', '30s'), + (r'check.gst-plugins-good.elements_deinterleave.test_2_channels$', '22s'), + (r'check.gst-plugins-good.elements_rtpjitterbuffer.test_fill_queue$', '22s'), + (r'check.gst-plugins-good.elements_splitmux.test_splitmuxsink_async$', '20s'), + (r'check.gst-plugins-good.elements_videomixer.test_play_twice$', '22s'), + (r'check.gst-editing-services.nle_simple.test_one_after_other$', '40s'), + (r'check.gst-editing-services.nle_nleoperation.test_pyramid_operations_expandable', 'https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/67'), + (r'check.gst-editing-services.nle_nleoperation.test_complex_operations', 'https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/67'), + (r'check.gst-editing-services.nle_nleoperation.test_pyramid_operations', 'https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/67'), + (r'check.gst-editing-services.nle_nleoperation.test_pyramid_operations2', 'https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/67'), + (r'check.gst-editing-services.nle_complex.test_one_above_another', 'https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/67'), + (r'check.gst-editing-services.nle_nleoperation.test_simple_operation', 'https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/67'), + (r'check.gst-editing-services.seek_with_stop.check_clock_sync', 'https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/67'), + (r'check.gst-editing-services.edit_while_seeked_with_stop', 'https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/67'), + (r'check.gst-editing-services.seek_with_stop', 'https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/67'), + (r'check.gst-editing-services.nle_tempochange.test_tempochange_seek', 'https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/67'), + (r'check.gst-editing-services.nle_tempochange.test_tempochange_play', 'https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/67'), +] + +VALGRIND_BLACKLIST = [ + (r'check.gstreamer.gst_gstsystemclock.test_stress_cleanup_unschedule', '?'), + (r'check.gstreamer.gst_gstsystemclock.test_stress_reschedule', '?'), + (r'check.gstreamer.tools_gstinspect', '?'), + (r'check.gst-plugins-base.elements_videoscale', '?'), + (r'check.gst-plugins-base.pipelines_gl_launch_lines', '?'), + (r'check.gst-plugins-base.libs_gstgl', 'driver leaks / memory access'), + (r'check.gst-plugins-base.elements_gl', 'driver leaks / memory access'), + (r'check.gst-plugins-base.elements_libvisual', 'uninitialized memory access'), + (r'check.gst-plugins-base.generic_states', 'need to add gl elements to ignore list but only if using valgrind'), + (r'check.gst-plugins-good.elements_rtpjitterbuffer.test_push_backward_seq', 'flaky in valgrind'), + (r'check.gst-plugins-good.elements_rtpjitterbuffer.test_push_unordered', 'flaky in valgrind'), + (r'check.gst-plugins-bad.elements_assrender', '?'), + (r'check.gst-plugins-bad.elements_camerabin', '?'), + (r'check.gst-plugins-bad.elements_line21', '?'), + (r'check.gst-plugins-bad.elements_mpeg2enc', '?'), + (r'check.gst-plugins-bad.elements_mplex', '?'), + (r'check.gst-plugins-bad.elements_mxfmux', '?'), + (r'check.gst-plugins-bad.elements_x265enc', '?'), + (r'check.gst-plugins-bad.elements_zbar', '?'), + (r'check.gst-plugins-bad.elements_webrtcbin.test_data_channel_remote_notify', 'Need to fix leaks'), + (r'check.gst-plugins-bad.elements_webrtcbin.test_data_channel_pre_negotiated', 'Need to fix leaks'), + (r'check.gst-plugins-bad.elements_webrtcbin.test_data_channel_low_threshold', 'Need to fix leaks'), + (r'check.gst-plugins-bad.elements_webrtcbin.test_data_channel_transfer_data', 'Need to fix leaks'), + (r'check.gst-plugins-bad.elements_webrtcbin.test_data_channel_transfer_string', 'Need to fix leaks'), + (r'check.gst-plugins-bad.elements_webrtcbin.test_data_channel_create_after_negotiate', 'Need to fix leaks'), + (r'check.gst-plugins-bad.elements_webrtcbin.test_data_channel_max_message_size', 'Need to fix leaks'), + (r'check.gst-plugins-bad.elements_webrtcbin.test_renego_data_channel_add_stream', 'Need to fix leaks (possibly flaky)'), + (r'check.gst-libav.generic_plugin_test', '?'), + (r'check.gst-libav.generic_libavcodec_locking', '?'), + (r'check.gst-libav.elements_avdemux_ape', '?'), + (r'check.gst-editing-services.pythontests', 'Need to figure out how to introduce python suppressions'), + (r'check.gst-editing-services.check_keyframes_in_compositor_two_sources', 'Valgrind exit with an exitcode 20 but shows no issue: https://gitlab.freedesktop.org/thiblahute/gst-editing-services/-/jobs/4079972') +] + +BLACKLIST = [ + (r'check.gstreamer.gst_gstsystemclock.test_stress_cleanup_unschedule', 'flaky under high server load'), + (r'check.gstreamer.gst_gstsystemclock.test_stress_reschedule', 'flaky under high server load'), + (r'check.gstreamer.pipelines_seek.test_loopback_2$', '?'), + (r'check.gstreamer.gst_gstelement.test_foreach_pad$', '?'), + (r'check.gstreamer.libs_baseparse.parser_pull_short_read$', '?'), + (r'check.gst-plugins-base.elements_multisocketsink.test_sending_buffers_with_9_gstmemories$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/issues/525'), + (r'check.gst-plugins-base.elements_multisocketsink.test_client_next_keyframe$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/issues/516'), + (r'check.gst-plugins-base.elements_multisocketsink.test_add_client$', ''), + (r'check.gst-plugins-base.libs_gstglcolorconvert.test_reorder_buffer$', '?'), + (r'check.gst-plugins-base.elements_audiotestsrc.test_layout$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/issues/535'), + (r'check.gst-plugins-good.elements_souphttpsrc.test_icy_stream$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/issues/307'), + (r'check.gst-plugins-good.elements_rtpbin.test_sender_eos$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/issues/527'), + (r'check.gst-plugins-good.elements_rtpbin.test_cleanup_recv$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/issues/546'), + (r'check.gst-plugins-good.elements_flvmux.test_incrementing_timestamps$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/issues/530'), + (r'check.gst-plugins-good.elements_flvmux.test_video_caps_late$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/issues/543'), + (r'check.gst-plugins-base.elements_appsrc.test_appsrc_blocked_on_caps$', '?'), + (r'check.gst-plugins-good.elements_splitmux.test_splitmuxsink$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/issues/626'), + (r'check.gst-plugins-good.elements_splitmux.test_splitmuxsrc_sparse_streams$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/issues/544'), + (r'check.gst-plugins-good.elements_splitmux.test_splitmuxsrc_caps_change$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/issues/547'), + (r'check.gst-plugins-bad.elements_dtls.test_data_transfer$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/issues/811'), + (r'check.gst-plugins-bad.elements_dtls.test_create_and_unref$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/issues/811'), + (r'check.gst-plugins-bad.elements_camerabin.test_image_video_cycle$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/issues/864'), + (r'check.gst-plugins-bad.elements_camerabin.test_single_video_recording$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/issues/864#note_101558'), + (r'check.gst-plugins-bad.elements_camerabin.test_multiple_video_recordings$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/issues/864#note_101646'), + (r'check.gst-plugins-bad.elements_curlhttpsrc.test_multiple_http_requests$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/issues/932'), + (r'check.gst-plugins-good.elements_rtpsession.test_multiple_senders_roundrobin_rbs$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/issues/548'), + (r'check.gst-plugins-bad.elements_shm.test_shm_live$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/issues/847'), + (r'check.gst-plugins-good.elements_splitmux.test_splitmuxsink_async$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/issues/531'), + (r'check.gst-plugins-bad.elements_netsim.netsim_stress$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/issues/849'), + (r'check.gst-editing-services.nle_complex.test_one_expandable_another$', 'https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/55'), + (r'check.gst-editing-services.nle_simple.test_simplest$', 'https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/57'), + (r'check.gst-editing-services.ges_basic.test_ges_pipeline_change_state$', 'https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/58'), + (r'check.gst-editing-services.pythontests.pyunittest.python.test_timeline.TestTransitions.test_transition_type$', 'https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/62'), + (r'check.gst-editing-services.pythontests.pyunittest.python.test_timeline.TestTransitions.test_auto_transition$', 'https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/63'), + (r'check.gst-plugins-base.pipelines_tcp.test_that_tcpserversink_and_tcpclientsrc_are_symmetrical$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/issues/221'), + (r'check.gstreamer.elements_capsfilter.test_unfixed_downstream_caps$', 'https://gitlab.freedesktop.org/gstreamer/gstreamer/issues/335'), + (r'check.gst-rtsp-server.gst_rtspclientsink.test_record$', 'https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/issues/55'), + (r'check.gst-rtsp-server.gst_rtspserver.test_shared_udp$', 'https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/issues/61'), + (r'check.gst-plugins-base.elements_audiomixer.test_flush_start_flush_stop$', 'https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/issues/555'), + (r'check.gstreamer-sharp.SdpTests$', 'https://gitlab.freedesktop.org/gstreamer/gstreamer-sharp/issues/17'), + (r'check.gst-devtools.validate.launcher_tests.test_validate.launch_pipeline.not_negotiated.caps_query_failure.play_15s$', '?'), + (r'check.gst-editing-services.nle_simple.test_one_bin_after_other$', 'https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/66'), + (r'check.gstreamer-vaapi.*$', 'only run the tests explicitly'), +] + + +def setup_tests(test_manager, options): + if options.gst_check_leak_trace_testnames == 'known-not-leaky': + options.gst_check_leak_trace_testnames = KNOWN_NOT_LEAKY + test_manager.set_default_blacklist(BLACKLIST) + if options.valgrind: + test_manager.set_default_blacklist(VALGRIND_BLACKLIST) + if options.long_limit <= utils.LONG_TEST: + test_manager.set_default_blacklist(LONG_VALGRIND_TESTS) + + test_manager.register_tests() + return True diff --git a/validate/launcher/testsuites/meson.build b/validate/launcher/testsuites/meson.build new file mode 100644 index 0000000000..069297e2ec --- /dev/null +++ b/validate/launcher/testsuites/meson.build @@ -0,0 +1,3 @@ +install_data(sources: ['check.py', 'pyunittest.py'], + install_dir: _launcherdir + '/testsuites') + diff --git a/validate/launcher/testsuites/pyunittest.py b/validate/launcher/testsuites/pyunittest.py new file mode 100644 index 0000000000..cd24e9be6b --- /dev/null +++ b/validate/launcher/testsuites/pyunittest.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2018,Thibault Saunier +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +TEST_MANAGER = "pyunittest" + + +def setup_tests(test_manager, options): + """Sets up python unit testsuite from external configuration.""" + test_manager.list_tests() + return True diff --git a/validate/launcher/utils.py b/validate/launcher/utils.py new file mode 100644 index 0000000000..09166bd5b9 --- /dev/null +++ b/validate/launcher/utils.py @@ -0,0 +1,1826 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2013,Thibault Saunier +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. +""" Some utilies. """ + +try: + import config +except ImportError: + from . import config + +import json +import numbers +import os +import platform +import re +import shutil +import shlex +import signal +import subprocess +import sys +import tempfile +import time +import urllib.request +import urllib.error +import urllib.parse + +from .loggable import Loggable, warning +from operator import itemgetter +from xml.etree import ElementTree +from collections import defaultdict +from fractions import Fraction + + +GST_SECOND = int(1000000000) +DEFAULT_TIMEOUT = 30 + +DEFAULT_MAIN_DIR = os.path.join(config.BUILDDIR, "subprojects", "gst-integration-testsuites") +DEFAULT_GST_QA_ASSETS = os.path.join(config.SRCDIR, "subprojects", "gst-integration-testsuites") +USING_SUBPROJECT = os.path.exists(os.path.join(config.BUILDDIR, "subprojects", "gst-integration-testsuites")) +if not USING_SUBPROJECT: + DEFAULT_MAIN_DIR = os.path.join(os.path.expanduser("~"), "gst-validate") + DEFAULT_GST_QA_ASSETS = os.path.join(DEFAULT_MAIN_DIR, "gst-integration-testsuites") + +DEFAULT_MAIN_DIR = os.environ.get('GST_VALIDATE_LAUNCHER_MAIN_DIR', DEFAULT_MAIN_DIR) +DEFAULT_TESTSUITES_DIRS = [os.path.join(DEFAULT_GST_QA_ASSETS, "testsuites")] + + +DISCOVERER_COMMAND = "gst-discoverer-1.0" +# Use to set the duration from which a test is considered as being 'long' +LONG_TEST = 40 + + +class Result(object): + NOT_RUN = "Not run" + FAILED = "Failed" + TIMEOUT = "Timeout" + PASSED = "Passed" + SKIPPED = "Skipped" + KNOWN_ERROR = "Known error" + + +class Protocols(object): + HTTP = "http" + FILE = "file" + PUSHFILE = "pushfile" + HLS = "hls" + DASH = "dash" + RTSP = "rtsp" + IMAGESEQUENCE = "imagesequence" + + @staticmethod + def needs_clock_sync(protocol): + if protocol in [Protocols.HLS, Protocols.DASH]: + return True + + return False + + +def is_tty(): + return hasattr(sys.stdout, 'isatty') and sys.stdout.isatty() + + +def supports_ansi_colors(): + if 'GST_VALIDATE_LAUNCHER_FORCE_COLORS' in os.environ: + return True + + platform = sys.platform + supported_platform = platform != 'win32' or 'ANSICON' in os.environ + if not supported_platform or not is_tty(): + return False + return True + + +class Colors(object): + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + + +def desactivate_colors(): + Colors.HEADER = '' + Colors.OKBLUE = '' + Colors.OKGREEN = '' + Colors.WARNING = '' + Colors.FAIL = '' + Colors.ENDC = '' + + +if not supports_ansi_colors(): + desactivate_colors() + + +def mkdir(directory): + try: + os.makedirs(directory) + except os.error: + pass + + +def which(name, extra_path=None): + exts = [_f for _f in os.environ.get('PATHEXT', '').split(os.pathsep) if _f] + path = os.environ.get('PATH', '') + if extra_path: + path = extra_path + os.pathsep + path + if not path: + return [] + + for p in path.split(os.pathsep): + p = os.path.join(p, name) + if os.access(p, os.X_OK): + return p + for e in exts: + pext = p + e + if os.access(pext, os.X_OK): + return pext + return None + + +def get_color_for_result(result): + if result is Result.FAILED: + color = Colors.FAIL + elif result is Result.TIMEOUT: + color = Colors.WARNING + elif result is Result.PASSED: + color = Colors.OKGREEN + else: + color = Colors.OKBLUE + + return color + + +last_carriage_return_len = 0 + + +def printc(message, color="", title=False, title_char='', end="\n"): + global last_carriage_return_len + if title or title_char: + length = 0 + for l in message.split("\n"): + if len(l) > length: + length = len(l) + if length == 0: + length = len(message) + + needed_spaces = ' ' * max(0, last_carriage_return_len - length) + if title is True: + message = length * "=" + needed_spaces + "\n" \ + + str(message) + "\n" + length * '=' + else: + message = str(message) + needed_spaces + "\n" + \ + length * title_char + + if hasattr(message, "result") and color == '': + color = get_color_for_result(message.result) + + if not is_tty(): + end = "\n" + + message = str(message) + message += ' ' * max(0, last_carriage_return_len - len(message)) + if end == '\r': + term_width = shutil.get_terminal_size((80, 20))[0] + if len(message) > term_width: + message = message[0:term_width - 2] + '…' + last_carriage_return_len = len(message) + else: + last_carriage_return_len = 0 + sys.stdout.write(color + str(message) + Colors.ENDC + end) + sys.stdout.flush() + + +def launch_command(command, color=None, fails=False): + printc(command, Colors.OKGREEN, True) + res = os.system(command) + if res != 0 and fails is True: + raise subprocess.CalledProcessError(res, "%s failed" % command) + + +def path2url(path): + return urllib.parse.urljoin('file:', urllib.request.pathname2url(path)) + + +def is_windows(): + platname = platform.system().lower() + return platname == 'windows' or 'mingw' in platname + + +def url2path(url): + path = urllib.parse.urlparse(url).path + if "win32" in sys.platform: + if path[0] == '/': + return path[1:] # We need to remove the first '/' on windows + path = urllib.parse.unquote(path) + return path + + +def isuri(string): + url = urllib.parse.urlparse(string) + if url.scheme != "" and url.scheme != "": + return True + + return False + + +def touch(fname, times=None): + with open(fname, 'a'): + os.utime(fname, times) + + +def get_subclasses(klass, env): + subclasses = [] + for symb in env.items(): + try: + if issubclass(symb[1], klass) and not symb[1] is klass: + subclasses.append(symb[1]) + except TypeError: + pass + + return subclasses + + +def TIME_ARGS(time): + return "%u:%02u:%02u.%09u" % (time / (GST_SECOND * 60 * 60), + (time / (GST_SECOND * 60)) % 60, + (time / GST_SECOND) % 60, + time % GST_SECOND) + + +def look_for_file_in_source_dir(subdir, name): + root_dir = os.path.abspath(os.path.dirname( + os.path.join(os.path.dirname(os.path.abspath(__file__))))) + p = os.path.join(root_dir, subdir, name) + if os.path.exists(p): + return p + + return None + + +# Returns the path $top_src_dir/@subdir/@name if running from source, or +# $DATADIR/gstreamer-1.0/validate/@name if not +def get_data_file(subdir, name): + # Are we running from sources? + p = look_for_file_in_source_dir(subdir, name) + if p: + return p + + # Look in system data dirs + p = os.path.join(config.DATADIR, 'gstreamer-1.0', 'validate', name) + if os.path.exists(p): + return p + + return None + +# +# Some utilities to parse gst-validate output # +# + + +def gsttime_from_tuple(stime): + return int((int(stime[0]) * 3600 + int(stime[1]) * 60 + int(stime[2])) * GST_SECOND + int(stime[3])) + + +timeregex = re.compile(r'(?P<_0>.+):(?P<_1>.+):(?P<_2>.+)\.(?P<_3>.+)') + + +def parse_gsttimeargs(time): + stime = list(map(itemgetter(1), sorted( + timeregex.match(time).groupdict().items()))) + return int((int(stime[0]) * 3600 + int(stime[1]) * 60 + int(stime[2])) * GST_SECOND + int(stime[3])) + + +def get_duration(media_file): + + duration = 0 + res = '' + try: + res = subprocess.check_output( + [DISCOVERER_COMMAND, media_file]).decode() + except subprocess.CalledProcessError: + # gst-media-check returns !0 if seeking is not possible, we do not care + # in that case. + pass + + for l in res.split('\n'): + if "Duration: " in l: + duration = parse_gsttimeargs(l.replace("Duration: ", "")) + break + + return duration + + +def get_scenarios(): + GST_VALIDATE_COMMAND = "gst-validate-1.0" + os.system("%s --scenarios-defs-output-file %s" % (GST_VALIDATE_COMMAND, + )) + + +def get_gst_build_valgrind_suppressions(): + if hasattr(get_gst_build_valgrind_suppressions, "data"): + return get_gst_build_valgrind_suppressions.data + + get_gst_build_valgrind_suppressions.data = [] + if not os.path.exists(os.path.join(config.SRCDIR, "subprojects")): + return get_gst_build_valgrind_suppressions.data + + for suppression_path in ["gstreamer/tests/check/gstreamer.supp", + "gst-plugins-base/tests/check/gst-plugins-base.supp", + "gst-plugins-good/tests/check/gst-plugins-good.supp", + "gst-plugins-bad/tests/check/gst-plugins-bad.supp", + "gst-plugins-ugly/tests/check/gst-plugins-ugly.supp", + "gst-libav/tests/check/gst-libav.supp", + "gst-devtools/validate/data/gstvalidate.supp", + "libnice/tests/libnice.supp", + "libsoup/tests/libsoup.supp", + "glib/glib.supp", + "gst-python/testsuite/gstpython.supp", + "gst-python/testsuite/python.supp", + ]: + suppression = os.path.join(config.SRCDIR, "subprojects", suppression_path) + if os.path.exists(suppression): + get_gst_build_valgrind_suppressions.data.append(suppression) + + return get_gst_build_valgrind_suppressions.data + + +class BackTraceGenerator(Loggable): + __instance = None + _command_line_regex = re.compile(r'Command Line: (.*)\n') + _timestamp_regex = re.compile(r'Timestamp: .*\((\d*)s ago\)') + _pid_regex = re.compile(r'PID: (\d+) \(.*\)') + + def __init__(self): + Loggable.__init__(self) + + self.in_flatpak = os.path.exists("/usr/manifest.json") + if self.in_flatpak: + coredumpctl = ['flatpak-spawn', '--host', 'coredumpctl'] + else: + coredumpctl = ['coredumpctl'] + + try: + subprocess.check_output(coredumpctl) + self.coredumpctl = coredumpctl + except Exception as e: + self.warning(e) + self.coredumpctl = None + self.gdb = shutil.which('gdb') + + @classmethod + def get_default(cls): + if not cls.__instance: + cls.__instance = BackTraceGenerator() + + return cls.__instance + + def get_trace(self, test): + if not test.process.returncode: + return self.get_trace_on_running_process(test) + + if self.coredumpctl: + return self.get_trace_from_systemd(test) + + self.debug("coredumpctl not present, and it is the only" + " supported way to get backtraces for now.") + return None + + def get_trace_on_running_process(self, test): + if not self.gdb: + return "Can not generate stack trace as `gdb` is not" \ + "installed." + + gdb = ['gdb', '-ex', 't a a bt', '-batch', + '-p', str(test.process.pid)] + + try: + return subprocess.check_output( + gdb, stderr=subprocess.STDOUT, timeout=30).decode() + except Exception as e: + return "Could not run `gdb` on process (pid: %d):\n%s" % ( + test.process.pid, e) + + def get_trace_from_systemd(self, test): + for ntry in range(10): + if ntry != 0: + # Loopping, it means we conceder the logs might not be ready + # yet. + time.sleep(1) + + if not self.in_flatpak: + coredumpctl = self.coredumpctl + ['info', str(test.process.pid)] + else: + newer_than = time.strftime("%a %Y-%m-%d %H:%M:%S %Z", time.localtime(test._starting_time)) + coredumpctl = self.coredumpctl + ['info', os.path.basename(test.command[0]), + '--since', newer_than] + + try: + info = subprocess.check_output(coredumpctl, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError: + # The trace might not be ready yet + time.sleep(1) + continue + + info = info.decode() + try: + pid = self._pid_regex.findall(info)[0] + except IndexError: + self.debug("Backtrace could not be found yet, trying harder.") + continue + + application = test.process.args[0] + command_line = BackTraceGenerator._command_line_regex.findall(info)[0] + if shlex.split(command_line)[0] != application: + self.debug("PID: %s -- executable %s != test application: %s" % ( + pid, command_line[0], test.application)) + # The trace might not be ready yet + continue + + if not BackTraceGenerator._timestamp_regex.findall(info): + self.debug("Timestamp %s is more than 1min old", + re.findall(r'Timestamp: .*', info)) + # The trace might not be ready yet + continue + + bt_all = None + if self.gdb: + try: + with tempfile.NamedTemporaryFile() as stderr: + coredump = subprocess.check_output(self.coredumpctl + ['dump', pid], + stderr=stderr) + + with tempfile.NamedTemporaryFile() as tf: + tf.write(coredump) + tf.flush() + gdb = ['gdb', '-ex', 't a a bt', '-ex', 'quit', application, tf.name] + bt_all = subprocess.check_output( + gdb, stderr=subprocess.STDOUT).decode() + + info += "\nThread apply all bt:\n\n%s" % ( + bt_all.replace('\n', '\n' + 15 * ' ')) + except Exception as e: + self.error("Could not get backtrace from gdb: %s" % e) + + return info + + return None + + +ALL_GITLAB_ISSUES = defaultdict(list) + + +def check_bugs_resolution(bugs_definitions): + bugz = {} + gitlab_issues = defaultdict(list) + + regexes = {} + for regex, bugs in bugs_definitions: + if isinstance(bugs, str): + bugs = [bugs] + + for bug in bugs: + url = urllib.parse.urlparse(bug) + + if "gitlab" in url.netloc: + components = [c for c in url.path.split('/') if c] + if len(components) != 4: + printc("\n + %s \n --> bug: %s\n --> Status: Not a proper gitlab report" % (regex, bug), + Colors.WARNING) + continue + project_id = components[0] + '%2F' + components[1] + issue_id = components[3] + + gitlab_url = "https://%s/api/v4/projects/%s/issues/%s" % (url.hostname, project_id, issue_id) + if gitlab_url in ALL_GITLAB_ISSUES: + continue + gitlab_issues[gitlab_url].append(regex) + ALL_GITLAB_ISSUES[gitlab_url].append(regex) + continue + + if "bugzilla" not in url.netloc: + continue + + query = urllib.parse.parse_qs(url.query) + _id = query.get('id') + if not _id: + printc("\n + '%s' -- Can't check bug '%s'" % + (regex, bug), Colors.WARNING) + continue + + if isinstance(_id, list): + _id = _id[0] + + regexes[_id] = (regex, bug) + url_parts = tuple(list(url)[:3] + ['', '', '']) + ids = bugz.get(url_parts, []) + ids.append(_id) + bugz[url_parts] = ids + + res = True + for gitlab_url, regexe in gitlab_issues.items(): + try: + issue = json.load(urllib.request.urlopen(gitlab_url)) + except Exception as e: + printc("\n + Could not properly check bugs status for: %s (%s)" + % (gitlab_url, e), Colors.FAIL) + continue + + if issue['state'] in ['closed']: + printc("\n + %s \n --> %s: '%s'\n ==> Bug CLOSED already (status: %s)" % ( + regexe, issue['web_url'], issue['title'], issue['state']), Colors.FAIL) + + res = False + + for url_parts, ids in bugz.items(): + url_parts = list(url_parts) + query = {'id': ','.join(ids)} + query['ctype'] = 'xml' + url_parts[4] = urllib.parse.urlencode(query) + try: + res = urllib.request.urlopen(urllib.parse.urlunparse(url_parts)) + except Exception as e: + printc("\n + Could not properly check bugs status for: %s (%s)" + % (urllib.parse.urlunparse(url_parts), e), Colors.FAIL) + continue + + root = ElementTree.fromstring(res.read()) + bugs = root.findall('./bug') + + if len(bugs) != len(ids): + printc("\n + Could not properly check bugs status on server %s" % + urllib.parse.urlunparse(url_parts), Colors.FAIL) + continue + + for bugelem in bugs: + status = bugelem.findtext('./bug_status') + bugid = bugelem.findtext('./bug_id') + regex, bug = regexes[bugid] + desc = bugelem.findtext('./short_desc') + + if not status: + printc("\n + %s \n --> bug: %s\n --> Status: UNKNOWN" % (regex, bug), + Colors.WARNING) + continue + + if not status.lower() in ['new', 'verified']: + printc("\n + %s \n --> bug: #%s: '%s'\n ==> Bug CLOSED already (status: %s)" % ( + regex, bugid, desc, status), Colors.WARNING) + + res = False + + printc("\n + %s \n --> bug: #%s: '%s'\n --> Status: %s" % ( + regex, bugid, desc, status), Colors.OKGREEN) + + if not res: + printc("\n==> Some bugs marked as known issues have been closed!", Colors.FAIL) + + return res + + +def kill_subprocess(owner, process, timeout, subprocess_ids=None): + if process is None: + return + + stime = time.time() + res = process.poll() + waittime = 0.05 + killsig = None + if not is_windows(): + killsig = signal.SIGINT + while res is None: + try: + owner.debug("Subprocess is still alive, sending KILL signal") + if is_windows(): + subprocess.call( + ['taskkill', '/F', '/T', '/PID', str(process.pid)]) + else: + if subprocess_ids: + for subprocess_id in subprocess_ids: + os.kill(subprocess_id, killsig) + else: + process.send_signal(killsig) + time.sleep(waittime) + waittime *= 2 + except OSError: + pass + + if not is_windows() and time.time() - stime > timeout / 4: + killsig = signal.SIGKILL + if time.time() - stime > timeout: + printc("Could not kill %s subprocess after %s second" + " Something is really wrong, => EXITING" + % (owner, timeout), Colors.FAIL) + + return + res = process.poll() + + return res + + +def format_config_template(extra_data, config_text, test_name): + # Variables available for interpolation inside config blocks. + + extra_vars = extra_data.copy() + + if 'validate-flow-expectations-dir' in extra_vars and \ + 'validate-flow-actual-results-dir' in extra_vars: + expectations_dir = os.path.join(extra_vars['validate-flow-expectations-dir'], + test_name.replace('.', os.sep)) + actual_results_dir = os.path.join(extra_vars['validate-flow-actual-results-dir'], + test_name.replace('.', os.sep)) + extra_vars['validateflow'] = "validateflow, expectations-dir=\"%s\", actual-results-dir=\"%s\"" % (expectations_dir, actual_results_dir) + + if 'ssim-results-dir' in extra_vars: + ssim_results = extra_vars['ssim-results-dir'] + extra_vars['ssim'] = "validatessim, result-output-dir=\"%s\", output-dir=\"%s\"" % ( + os.path.join(ssim_results, test_name.replace('.', os.sep), 'diff-images'), + os.path.join(ssim_results, test_name.replace('.', os.sep), 'images'), + ) + + return config_text % extra_vars + + +def get_fakesink_for_media_type(media_type, needs_clock=False): + extra = "" + if media_type == "video" and needs_clock: + extra = 'max-lateness=20000000' + + return f"fake{media_type}sink sync={needs_clock} {extra}" + + +class InvalidValueError(ValueError): + """Received value is invalid""" + def __init__(self, name, value, expect): + ValueError.__init__( + self, "Invalid value {!r} for {}. Expect {}.".format( + value, name, expect)) + + +def wrong_type_for_arg(val, expect_type_name, arg_name): + """Raise exception in response to a wrong argument type""" + raise TypeError( + "Expect a {} type for the '{}' argument. Received a {} type." + "".format(expect_type_name, arg_name, type(val).__name__)) + + +class DeserializeError(Exception): + """Receive an incorrectly serialized value""" + MAX_LEN = 20 + + def __init__(self, read, reason): + if len(read) > self.MAX_LEN: + read = read[:self.MAX_LEN] + "..." + Exception.__init__( + self, "Could not deserialize the string ({}) because it {}." + "".format(read, reason)) + + +class GstStructure(Loggable): + """ + This implementation has been copied from OpenTimelineIO. + + Note that the types are to correspond to GStreamer/GES GTypes, + rather than python types. + + Current supported GTypes: + GType Associated Accepted + Python type aliases + ====================================== + gint int int, i + glong int + gint64 int + guint int uint, u + gulong int + guint64 int + gfloat float float, f + gdouble float double, d + gboolean bool boolean, + bool, b + string str or None str, s + GstFraction str or fraction + Fraction + GstStructure GstStructure structure + schema + GstCaps GstCaps + schema + + Note that other types can be given: these must be given as strings + and the user will be responsible for making sure they are already in + a serialized form. + """ + + INT_TYPES = ("int", "glong", "gint64") + UINT_TYPES = ("uint", "gulong", "guint64") + FLOAT_TYPES = ("float", "double") + BOOLEAN_TYPE = "boolean" + FRACTION_TYPE = "fraction" + STRING_TYPE = "string" + STRUCTURE_TYPE = "structure" + CAPS_TYPE = "GstCaps" + KNOWN_TYPES = INT_TYPES + UINT_TYPES + FLOAT_TYPES + ( + BOOLEAN_TYPE, FRACTION_TYPE, STRING_TYPE, STRUCTURE_TYPE, + CAPS_TYPE) + + TYPE_ALIAS = { + "i": "int", + "gint": "int", + "u": "uint", + "guint": "uint", + "f": "float", + "gfloat": "float", + "d": "double", + "gdouble": "double", + "b": BOOLEAN_TYPE, + "bool": BOOLEAN_TYPE, + "gboolean": BOOLEAN_TYPE, + "GstFraction": FRACTION_TYPE, + "str": STRING_TYPE, + "s": STRING_TYPE, + "GstStructure": STRUCTURE_TYPE + } + + def __init__(self, name=None, fields=None): + if name is None: + name = "Unnamed" + if fields is None: + fields = {} + if type(name) is not str: + wrong_type_for_arg(name, "str", "name") + self._check_name(name) + self.name = name + try: + fields = dict(fields) + except (TypeError, ValueError): + wrong_type_for_arg(fields, "dict", "fields") + self.fields = {} + for key in fields: + entry = fields[key] + if type(entry) is not tuple: + try: + entry = tuple(entry) + except (TypeError, ValueError): + raise TypeError( + "Expect dict to be filled with tuple-like " + "entries") + if len(entry) != 2: + raise TypeError( + "Expect dict to be filled with 2-entry tuples") + self.set(key, *entry) + + def __repr__(self): + return "GstStructure({!r}, {!r})".format(self.name, self.fields) + + UNKNOWN_PREFIX = "[UNKNOWN]" + + @classmethod + def _make_type_unknown(cls, _type): + return cls.UNKNOWN_PREFIX + _type + # note the sqaure brackets make the type break the TYPE_FORMAT + + @classmethod + def _is_unknown_type(cls, _type): + return _type[:len(cls.UNKNOWN_PREFIX)] == cls.UNKNOWN_PREFIX + + @classmethod + def _get_unknown_type(cls, _type): + return _type[len(cls.UNKNOWN_PREFIX):] + + def _field_to_str(self, key): + """Return field in a serialized form""" + _type, value = self.fields[key] + if type(key) is not str: + raise TypeError("Found a key that is not a str type") + if type(_type) is not str: + raise TypeError( + "Found a type name that is not a str type") + self._check_key(key) + _type = self.TYPE_ALIAS.get(_type, _type) + if self._is_unknown_type(_type): + _type = self._get_unknown_type(_type) + self._check_type(_type) + self._check_unknown_typed_value(value) + # already in serialized form + else: + self._check_type(_type) + value = self.serialize_value(_type, value) + return "{}=({}){}".format(key, _type, value) + + def _fields_to_str(self): + write = [] + for key in self.fields: + write.append(", {}".format(self._field_to_str(key))) + return "".join(write) + + def _name_to_str(self): + """Return the name in a serialized form""" + return self._check_name(self.name) + + def __str__(self): + """Emulates gst_structure_to_string""" + return "{}{};".format(self._name_to_str(), self._fields_to_str()) + + def get_type_name(self, key): + """Return the field type""" + _type = self.fields[key][0] + return _type + + def get_value(self, key): + """Return the field value""" + value = self.fields[key][1] + return value + + def __getitem__(self, key): + return self.get_value(key) + + def __len__(self): + return len(self.fields) + + @staticmethod + def _val_type_err(typ, val, expect): + raise TypeError( + "Received value ({!s}) is a {} rather than a {}, even " + "though the {} type was given".format( + val, type(val).__name__, expect, typ)) + + def set(self, key, _type, value): + """Set a field to the given typed value""" + if type(key) is not str: + wrong_type_for_arg(key, "str", "key") + if type(_type) is not str: + wrong_type_for_arg(_type, "str", "_type") + _type = self.TYPE_ALIAS.get(_type, _type) + if self.fields.get(key) == (_type, value): + return + self._check_key(key) + type_is_unknown = True + if self._is_unknown_type(_type): + # this can happen if the user is setting a GstStructure + # using a preexisting GstStructure, the type will then + # be passed and marked as unknown + _type = self._get_unknown_type(_type) + self._check_type(_type) + else: + self._check_type(_type) + if _type in self.INT_TYPES: + type_is_unknown = False + if not isinstance(value, int): + self._val_type_err(_type, value, "int") + elif _type in self.UINT_TYPES: + type_is_unknown = False + if not isinstance(value, int): + self._val_type_err(_type, value, "int") + if value < 0: + raise InvalidValueError( + "value", value, "a positive integer for {} " + "types".format(_type)) + elif _type in self.FLOAT_TYPES: + type_is_unknown = False + if type(value) is not float: + self._val_type_err(_type, value, "float") + elif _type == self.BOOLEAN_TYPE: + type_is_unknown = False + if type(value) is not bool: + self._val_type_err(_type, value, "bool") + elif _type == self.FRACTION_TYPE: + type_is_unknown = False + if type(value) is Fraction: + value = value + elif type(value) is str: + try: + Fraction(value) + except ValueError: + raise InvalidValueError( + "value", value, "a fraction for the {} " + "types".format(_type)) + else: + self._val_type_err(_type, value, "Fraction or str") + elif _type == self.STRING_TYPE: + type_is_unknown = False + if value is not None and type(value) is not str: + self._val_type_err(_type, value, "str or None") + elif _type == self.STRUCTURE_TYPE: + type_is_unknown = False + if not isinstance(value, GstStructure): + self._val_type_err(_type, value, "GstStructure") + elif _type == self.CAPS_TYPE: + type_is_unknown = False + if not isinstance(value, GstCaps): + self._val_type_err(_type, value, "GstCaps") + if type_is_unknown: + self._check_unknown_typed_value(value) + warning('GstStructure', + "The GstStructure type {} with the value ({}) is " + "unknown. The value will be stored and serialized as " + "given.".format(_type, value)) + _type = self._make_type_unknown(_type) + self.fields[key] = (_type, value) + + def get(self, key, default=None): + """Return the raw value associated with key""" + if key in self.fields: + value = self.get_value(key) + return value + return default + + def get_typed(self, key, expect_type, default=None): + """ + Return the raw value associated with key if its type matches. + Raises a warning if a value exists under key but is of the + wrong type. + """ + if type(expect_type) is not str: + wrong_type_for_arg(expect_type, "str", "expect_type") + expect_type = self.TYPE_ALIAS.get(expect_type, expect_type) + if key in self.fields: + type_name = self.get_type_name(key) + if expect_type == type_name: + value = self.get_value(key) + return value + warning('GstStructure', + "The structure {} contains a value under {}, but is " + "a {}, rather than the expected {} type".format( + self.name, key, type_name, expect_type)) + return default + + def values(self): + """Return a list of all values contained in the structure""" + return [self.get_value(key) for key in self.fields] + + def values_of_type(self, _type): + """ + Return a list of all values contained of the given type in the + structure + """ + if type(_type) is not str: + wrong_type_for_arg(_type, "str", "_type") + _type = self.TYPE_ALIAS.get(_type, _type) + return [self.get_value(key) for key in self.fields + if self.get_type_name(key) == _type] + + ASCII_SPACES = r"(\\?[ \t\n\r\f\v])*" + END_FORMAT = r"(?P" + ASCII_SPACES + r")" + NAME_FORMAT = r"(?P[a-zA-Z][a-zA-Z0-9/_.:-]*)" + # ^Format requirement for the name of a GstStructure + SIMPLE_STRING = r"[a-zA-Z0-9_+/:.-]+" + # see GST_ASCII_CHARS (below) + KEY_FORMAT = r"(?P" + SIMPLE_STRING + r")" + # NOTE: GstStructure technically allows more general keys, but + # these can break the parsing. + TYPE_FORMAT = r"(?P" + SIMPLE_STRING + r")" + BASIC_VALUE_FORMAT = \ + r'(?P("(\\.|[^"])*")|(' + SIMPLE_STRING + r'))' + # consume simple string or a string between quotes. Second will + # consume anything that is escaped, including a '"' + # NOTE: \\. is used rather than \\" since: + # + '"start\"end;"' should be captured as '"start\"end"' since + # the '"' is escaped. + # + '"start\\"end;"' should be captured as '"start\\"' since the + # '\' is escaped, not the '"' + # In the fist case \\. will consume '\"', and in the second it will + # consumer '\\', as desired. The second would not work with just \\" + + @staticmethod + def _check_against_regex(check, regex, name): + if not regex.fullmatch(check): + raise InvalidValueError( + name, check, "to match the regular expression {}" + "".format(regex.pattern)) + return check + + NAME_REGEX = re.compile(NAME_FORMAT) + KEY_REGEX = re.compile(KEY_FORMAT) + TYPE_REGEX = re.compile(TYPE_FORMAT) + + @classmethod + def _check_name(cls, name): + return cls._check_against_regex(name, cls.NAME_REGEX, "name") + + @classmethod + def _check_key(cls, key): + return cls._check_against_regex(key, cls.KEY_REGEX, "key") + + @classmethod + def _check_type(cls, _type): + return cls._check_against_regex(_type, cls.TYPE_REGEX, "type") + + @classmethod + def _check_unknown_typed_value(cls, value): + if type(value) is not str: + cls._val_type_err("unknown", value, "string") + try: + # see if the value could be successfully parsed in again + ret_type, ret_val, _ = cls._parse_value(value, False) + except DeserializeError as err: + raise InvalidValueError( + "value", value, "unknown-typed values to be in a " + "serialized format ({!s})".format(err)) + else: + if ret_type is not None: + raise InvalidValueError( + "value", value, "unknown-typed values to *not* " + "start with a type specification, only the " + "serialized value should be given") + if ret_val != value: + raise InvalidValueError( + "value", value, "unknown-typed values to be the " + "same as its parsed value {}".format(ret_val)) + + PARSE_NAME_REGEX = re.compile( + ASCII_SPACES + NAME_FORMAT + END_FORMAT) + + @classmethod + def _parse_name(cls, read): + match = cls.PARSE_NAME_REGEX.match(read) + if match is None: + raise DeserializeError( + read, "does not start with a correct name") + name = match.group("name") + read = read[match.end("end"):] + return name, read + + @classmethod + def _parse_range_list_array(cls, read): + start = read[0] + end = {'[': ']', '{': '}', '<': '>'}.get(start) + read = read[1:] + values = [start, ' '] + first = True + while read and read[0] != end: + if first: + first = False + else: + if read and read[0] != ',': + DeserializeError( + read, "does not contain a comma between listed " + "items") + values.append(", ") + read = read[1:] + _type, value, read = cls._parse_value(read, False) + if _type is not None: + if cls._is_unknown_type(_type): + # remove unknown marker for serialization + _type = cls._get_unknown_type(_type) + values.extend(('(', _type, ')')) + values.append(value) + if not read: + raise DeserializeError( + read, "ended before {} could be found".format(end)) + read = read[1:] # skip past 'end' + match = cls.END_REGEX.match(read) # skip whitespace + read = read[match.end("end"):] + # NOTE: we are ignoring the incorrect cases where a range + # has 0, 1 or 4+ values! This is the users responsiblity. + values.extend((' ', end)) + return "".join(values), read + + FIELD_START_REGEX = re.compile( + ASCII_SPACES + KEY_FORMAT + ASCII_SPACES + r"=" + END_FORMAT) + FIELD_TYPE_REGEX = re.compile( + ASCII_SPACES + r"(\(" + ASCII_SPACES + TYPE_FORMAT + + ASCII_SPACES + r"\))?" + END_FORMAT) + FIELD_VALUE_REGEX = re.compile( + ASCII_SPACES + BASIC_VALUE_FORMAT + END_FORMAT) + END_REGEX = re.compile(END_FORMAT) + + @classmethod + def _parse_value(cls, read, deserialize=True): + match = cls.FIELD_TYPE_REGEX.match(read) + # match shouldn't be None since the (TYPE_FORMAT) is optional + # and the rest is just ASCII_SPACES + _type = match.group("type") + if _type is None and deserialize: + # if deserialize is False, the (type) is optional + raise DeserializeError( + read, "does not contain a valid '(type)' format") + _type = cls.TYPE_ALIAS.get(_type, _type) + type_is_unknown = True + read = read[match.end("end"):] + if read and read[0] in ('[', '{', '<'): + # range/list/array types + # this is an unknown type, even though _type itself may + # be known. e.g. a list on integers will have _type as 'int' + # but the corresponding value can not be deserialized as an + # integer + value, read = cls._parse_range_list_array(read) + if deserialize: + # prevent printing on subsequent calls if we find a + # list within a list, etc. + warning('GstStructure', + "GstStructure received a range/list/array of type " + "{}, which can not be deserialized. Storing the " + "value as {}.".format(_type, value)) + else: + match = cls.FIELD_VALUE_REGEX.match(read) + if match is None: + raise DeserializeError( + read, "does not have a valid value format") + read = read[match.end("end"):] + value = match.group("value") + if deserialize: + if _type in cls.KNOWN_TYPES: + type_is_unknown = False + try: + value = cls.deserialize_value(_type, value) + except DeserializeError as err: + raise DeserializeError( + read, "contains an invalid typed value " + "({!s})".format(err)) + else: + warning('GstStructure', + "GstStructure found a type {} that is unknown. " + "The corresponding value ({}) will not be " + "deserialized and will be stored as given." + "".format(_type, value)) + if type_is_unknown and _type is not None: + _type = cls._make_type_unknown(_type) + return _type, value, read + + @classmethod + def _parse_field(cls, read): + match = cls.FIELD_START_REGEX.match(read) + if match is None: + raise DeserializeError( + read, "does not have a valid 'key=...' format") + key = match.group("key") + read = read[match.end("end"):] + _type, value, read = cls._parse_value(read) + return key, _type, value, read + + @classmethod + def _parse_fields(cls, read): + if type(read) is not str: + wrong_type_for_arg(read, "str", "read") + fields = {} + while read and read[0] != ';': + if read and read[0] != ',': + DeserializeError( + read, "does not separate fields with commas") + read = read[1:] + key, _type, value, read = cls._parse_field(read) + fields[key] = (_type, value) + if read: + # read[0] == ';' + read = read[1:] + return fields, read + + @classmethod + def new_from_str(cls, read): + """ + Returns a new instance of GstStructure, based on the Gst library + function gst_structure_from_string. + Strings obtained from the GstStructure str() method can be + parsed in to recreate the original GstStructure. + """ + if type(read) is not str: + wrong_type_for_arg(read, "str", "read") + name, read = cls._parse_name(read) + fields = cls._parse_fields(read)[0] + return GstStructure(name=name, fields=fields) + + @staticmethod + def _val_read_err(typ, val): + raise DeserializeError( + val, "does not translated to the {} type".format(typ)) + + @classmethod + def deserialize_value(cls, _type, value): + """Return the value as the corresponding type""" + if type(_type) is not str: + wrong_type_for_arg(_type, "str", "_type") + if type(value) is not str: + wrong_type_for_arg(value, "str", "value") + _type = cls.TYPE_ALIAS.get(_type, _type) + if _type in cls.INT_TYPES or _type in cls.UINT_TYPES: + try: + value = int(value) + except ValueError: + cls._val_read_err(_type, value) + if _type in cls.UINT_TYPES and value < 0: + cls._val_read_err(_type, value) + elif _type in cls.FLOAT_TYPES: + try: + value = float(value) + except ValueError: + cls._val_read_err(_type, value) + elif _type == cls.BOOLEAN_TYPE: + try: + value = cls.deserialize_boolean(value) + except DeserializeError: + cls._val_read_err(_type, value) + elif _type == cls.FRACTION_TYPE: + try: + value = Fraction(value) + except ValueError: + cls._val_read_err(_type, value) + elif _type == cls.STRING_TYPE: + try: + value = cls.deserialize_string(value) + except DeserializeError as err: + raise DeserializeError( + value, "does not translate to a string ({!s})" + "".format(err)) + elif _type == cls.STRUCTURE_TYPE: + try: + value = cls.deserialize_structure(value) + except DeserializeError as err: + raise DeserializeError( + value, "does not translate to a GstStructure ({!s})" + "".format(err)) + elif _type == cls.CAPS_TYPE: + try: + value = cls.deserialize_caps(value) + except DeserializeError as err: + raise DeserializeError( + value, "does not translate to a GstCaps ({!s})" + "".format(err)) + else: + raise ValueError( + "The type {} is unknown, so the value ({}) can not " + "be deserialized.".format(_type, value)) + return value + + @classmethod + def serialize_value(cls, _type, value): + """Serialize the typed value as a string""" + if type(_type) is not str: + wrong_type_for_arg(_type, "str", "_type") + _type = cls.TYPE_ALIAS.get(_type, _type) + if _type in cls.INT_TYPES + cls.UINT_TYPES + cls.FLOAT_TYPES \ + + (cls.FRACTION_TYPE, ): + return str(value) + if _type == cls.BOOLEAN_TYPE: + return cls.serialize_boolean(value) + if _type == cls.STRING_TYPE: + return cls.serialize_string(value) + if _type == cls.STRUCTURE_TYPE: + return cls.serialize_structure(value) + if _type == cls.CAPS_TYPE: + return cls.serialize_caps(value) + raise ValueError( + "The type {} is unknown, so the value ({}) can not be " + "serialized.".format(_type, str(value))) + + # see GST_ASCII_IS_STRING in gst_private.h + GST_ASCII_CHARS = [ + ord(l) for l in "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + "_-+/:." + ] + LEADING_OCTAL_CHARS = [ord(l) for l in "0123"] + OCTAL_CHARS = [ord(l) for l in "01234567"] + + @classmethod + def serialize_string(cls, value): + """ + Emulates gst_value_serialize_string. + Accepts a bytes, str or None type. + Returns a str type. + """ + if value is not None and type(value) is not str: + wrong_type_for_arg(value, "None or str", "value") + return cls._wrap_string(value) + + @classmethod + def _wrap_string(cls, read): + if read is None: + return "NULL" + if read == "NULL": + return "\"NULL\"" + if type(read) is bytes: + pass + elif type(read) is str: + read = read.encode() + else: + wrong_type_for_arg(read, "None, str, or bytes", "read") + if not read: + return '""' + added_wrap = False + ser_string_list = [] + for byte in read: + if byte in cls.GST_ASCII_CHARS: + ser_string_list.append(chr(byte)) + elif byte < 0x20 or byte >= 0x7f: + ser_string_list.append("\\{:03o}".format(byte)) + added_wrap = True + else: + ser_string_list.append("\\" + chr(byte)) + added_wrap = True + if added_wrap: + ser_string_list.insert(0, '"') + ser_string_list.append('"') + return "".join(ser_string_list) + + @classmethod + def deserialize_string(cls, read): + """ + Emulates gst_value_deserialize_string. + Accepts a str type. + Returns a str or None type. + """ + if type(read) is not str: + wrong_type_for_arg(read, "str", "read") + if read == "NULL": + return None + if not read: + return "" + if read[0] != '"' or read[-1] != '"': + return read + return cls._unwrap_string(read) + + @classmethod + def _unwrap_string(cls, read): + """Emulates gst_string_unwrap""" + if type(read) is not bytes: + read_array = read.encode() + byte_list = [] + bytes_iter = iter(read_array) + + def next_byte(): + try: + return next(bytes_iter) + except StopIteration: + raise DeserializeError(read, "end unexpectedly") + + byte = next_byte() + if byte != ord('"'): + raise DeserializeError( + read, "does not start with '\"', but ends with '\"'") + while True: + byte = next_byte() + if byte in cls.GST_ASCII_CHARS: + byte_list.append(byte) + elif byte == ord('"'): + try: + next(bytes_iter) + except StopIteration: + # expect there to be no more bytes + break + raise DeserializeError( + read, "contains an un-escaped '\"' before the end") + elif byte == ord('\\'): + byte = next_byte() + if byte in cls.LEADING_OCTAL_CHARS: + # could be the start of an octal + byte2 = next_byte() + byte3 = next_byte() + if byte2 in cls.OCTAL_CHARS and byte3 in cls.OCTAL_CHARS: + nums = [b - ord('0') for b in (byte, byte2, byte3)] + byte = (nums[0] << 6) + (nums[1] << 3) + nums[2] + byte_list.append(byte) + else: + raise DeserializeError( + read, "contains the start of an octal " + "sequence but not the end") + else: + if byte == 0: + raise DeserializeError( + read, "contains a null byte after an escape") + byte_list.append(byte) + else: + raise DeserializeError( + read, "contains an unexpected un-escaped character") + out_str = bytes(bytearray(byte_list)) + try: + return out_str.decode() + except (UnicodeError, ValueError): + raise DeserializeError( + read, "contains invalid utf-8 byte sequences") + + @staticmethod + def serialize_boolean(value): + """ + Emulates gst_value_serialize_boolean. + Accepts bool type. + Returns a str type. + """ + if type(value) is not bool: + wrong_type_for_arg(value, "bool", "value") + if value: + return "true" + return "false" + + @staticmethod + def deserialize_boolean(read): + """ + Emulates gst_value_deserialize_boolean. + Accepts str type. + Returns a bool type. + """ + if type(read) is not str: + wrong_type_for_arg(read, "str", "read") + if read.lower() in ("true", "t", "yes", "1"): + return True + if read.lower() in ("false", "f", "no", "0"): + return False + raise DeserializeError(read, "is an unknown boolean value") + + @classmethod + def serialize_structure(cls, value): + """ + Emulates gst_value_serialize_structure. + Accepts a GstStructure. + Returns a str type. + """ + if not isinstance(value, GstStructure): + wrong_type_for_arg(value, "GstStructure", "value") + return cls._wrap_string(str(value)) + + @classmethod + def deserialize_structure(cls, read): + """ + Emulates gst_value_serialize_structure. + Accepts a str type. + Returns a GstStructure. + """ + if type(read) is not str: + wrong_type_for_arg(read, "str", "read") + if read[0] == '"': + # NOTE: since all GstStructure strings end with ';', we + # don't ever expect the above to *not* be true, but the + # GStreamer library allows for this case + try: + read = cls._unwrap_string(read) + # NOTE: in the GStreamer library, serialized + # GstStructure and GstCaps strings are sent to + # _priv_gst_value_parse_string with unescape set to + # TRUE. What this essentially does is replace "\x" with + # just "x". Since caps and structure strings should only + # contain printable ascii characters before they are + # passed to _wrap_string, this should be equivalent to + # calling _unwrap_string. Our method is more clearly a + # reverse of the serialization method. + except DeserializeError as err: + raise DeserializeError( + read, "could not be unwrapped as a string ({!s})" + "".format(err)) + return GstStructure.new_from_str(read) + + @classmethod + def serialize_caps(cls, value): + """ + Emulates gst_value_serialize_caps. + Accepts a GstCaps. + Returns a str type. + """ + if not isinstance(value, GstCaps): + wrong_type_for_arg(value, "GstCaps", "value") + return cls._wrap_string(str(value)) + + @classmethod + def deserialize_caps(cls, read): + """ + Emulates gst_value_serialize_caps. + Accepts a str type. + Returns a GstCaps. + """ + if type(read) is not str: + wrong_type_for_arg(read, "str", "read") + if read[0] == '"': + # can be not true if a caps only contains a single empty + # structure, or is ALL or NONE + try: + read = cls._unwrap_string(read) + except DeserializeError as err: + raise DeserializeError( + read, "could not be unwrapped as a string ({!s})" + "".format(err)) + return GstCaps.new_from_str(read) + + @staticmethod + def _escape_string(read): + """ + Emulates some of g_strescape's behaviour in + ges_marker_list_serialize + """ + # NOTE: in the original g_strescape, all the special characters + # '\b', '\f', '\n', '\r', '\t', '\v', '\' and '"' are escaped, + # and all characters in the range 0x01-0x1F and non-ascii + # characters are replaced by an octal sequence + # (similar to _wrap_string). + # However, a caps string should only contain printable ascii + # characters, so it should be sufficient to simply escape '\' + # and '"'. + escaped = ['"'] + for character in read: + if character in ('"', '\\'): + escaped.append('\\') + escaped.append(character) + escaped.append('"') + return "".join(escaped) + + @staticmethod + def _unescape_string(read): + """ + Emulates behaviour of _priv_gst_value_parse_string with + unescape set to TRUE. This should undo _escape_string + """ + if read[0] != '"': + return read + character_iter = iter(read) + + def next_char(): + try: + return next(character_iter) + except StopIteration: + raise DeserializeError(read, "ends unexpectedly") + + next_char() # skip '"' + unescaped = [] + while True: + character = next_char() + if character == '"': + break + if character == '\\': + unescaped.append(next_char()) + else: + unescaped.append(character) + return "".join(unescaped) + + +class GstCapsFeatures(): + """ + Mimicking a GstCapsFeatures. + """ + def __init__(self, *features): + """ + Initialize the GstCapsFeatures. + + 'features' should be a series of feature names as strings. + """ + self.is_any = False + self.features = [] + for feature in features: + if type(feature) is not str: + wrong_type_for_arg(feature, "strs", "features") + self._check_feature(feature) + self.features.append(feature) + # NOTE: if 'features' is a str, rather than a list of strs + # then this will iterate through all of its characters! But, + # a single character can not match the feature regular + # expression. + + def __getitem__(self, index): + return self.features[index] + + def __len__(self): + return len(self.features) + + @classmethod + def new_any(cls): + features = cls() + features.is_any = True + return features + + # Based on gst_caps_feature_name_is_valid + FEATURE_FORMAT = r"(?P[a-zA-Z]*:[a-zA-Z][a-zA-Z0-9]*)" + FEATURE_REGEX = re.compile(FEATURE_FORMAT) + + @classmethod + def _check_feature(cls, feature): + if not cls.FEATURE_REGEX.fullmatch(feature): + raise InvalidValueError( + "feature", feature, "to match the regular expression " + "{}".format(cls.FEATURE_REGEX.pattern)) + + PARSE_FEATURE_REGEX = re.compile( + r" *" + FEATURE_FORMAT + "(?P)") + + @classmethod + def new_from_str(cls, read): + """ + Returns a new instance of GstCapsFeatures, based on the Gst + library function gst_caps_features_from_string. + Strings obtained from the GstCapsFeatures str() method can be + parsed in to recreate the original GstCapsFeatures. + """ + if type(read) is not str: + wrong_type_for_arg(read, "str", "read") + if read == "ANY": + return cls.new_any() + first = True + features = [] + while read: + if first: + first = False + else: + if read[0] != ',': + DeserializeError( + read, "does not separate features with commas") + read = read[1:] + match = cls.PARSE_FEATURE_REGEX.match(read) + if match is None: + raise DeserializeError( + read, "does not match the regular expression {}" + "".format(cls.PARSE_FEATURE_REGEX.pattern)) + features.append(match.group("feature")) + read = read[match.end("end"):] + return cls(*features) + + def __repr__(self): + if self.is_any: + return "GstCapsFeatures.new_any()" + write = ["GstCapsFeatures("] + first = True + for feature in self.features: + if first: + first = False + else: + write.append(", ") + write.append(repr(feature)) + write.append(")") + return "".join(write) + + def __str__(self): + """Emulate gst_caps_features_to_string""" + if not self.features and self.is_any: + return "ANY" + write = [] + first = True + for feature in self.features: + if type(feature) is not str: + raise TypeError( + "Found a feature that is not a str type") + if first: + first = False + else: + write.append(", ") + write.append(feature) + return "".join(write) + + +class GstCaps: + GST_CAPS_FLAG_ANY = 1 << 4 + # from GST_MINI_OBJECT_FLAG_LAST + + def __init__(self, *structs): + """ + Initialize the GstCaps. + + 'structs' should be a series of GstStructures, and + GstCapsFeatures pairs: + struct0, features0, struct1, features1, ... + None may be given in place of a GstCapsFeatures, in which case + an empty features is assigned to the structure. + + Note, this instance will need to take ownership of any given + GstStructure or GstCapsFeatures. + """ + if len(structs) % 2: + raise InvalidValueError( + "*structs", structs, "an even number of arguments") + self.flags = 0 + self.structs = [] + struct = None + for index, arg in enumerate(structs): + if index % 2 == 0: + struct = arg + else: + self.append(struct, arg) + + def get_structure(self, index): + """Return the GstStructure at the given index""" + return self.structs[index][0] + + def get_features(self, index): + """Return the GstStructure at the given index""" + return self.structs[index][1] + + def __getitem__(self, index): + return self.get_structure(index) + + def __len__(self): + return len(self.structs) + + def __iter__(self): + for s in self.structs: + yield s + + @classmethod + def new_any(cls): + caps = cls() + caps.flags = cls.GST_CAPS_FLAG_ANY + return caps + + def is_any(self): + return self.flags & self.GST_CAPS_FLAG_ANY != 0 + + FEATURES_FORMAT = r"\((?P[^)]*)\)" + NAME_FEATURES_REGEX = re.compile( + GstStructure.ASCII_SPACES + GstStructure.NAME_FORMAT + + r"(" + FEATURES_FORMAT + r")?" + GstStructure.END_FORMAT) + + @classmethod + def new_from_str(cls, read): + """ + Returns a new instance of GstCaps, based on the Gst library + function gst_caps_from_string. + Strings obtained from the GstCaps str() method can be parsed in + to recreate the original GstCaps. + """ + if type(read) is not str: + wrong_type_for_arg(read, "str", "read") + if read == "ANY": + return cls.new_any() + if read in ("EMPTY", "NONE"): + return cls() + structs = [] + # restriction-caps is otherwise serialized in the format: + # "struct-name-nums(feature), " + # "field1=(type1)val1, field2=(type2)val2; " + # "struct-name-alphas(feature), " + # "fieldA=(typeA)valA, fieldB=(typeB)valB" + # Note the lack of ';' for the last structure, and the + # '(feature)' is optional. + # + # NOTE: gst_caps_from_string also accepts: + # "struct-name(feature" + # without the final ')', but this must be the end of the string, + # but we will require that this final ')' is still given + while read: + match = cls.NAME_FEATURES_REGEX.match(read) + if match is None: + raise DeserializeError( + read, "does not match the regular expression {}" + "".format(cls.NAME_FEATURE_REGEX.pattern)) + read = read[match.end("end"):] + name = match.group("name") + features = match.group("features") + # NOTE: features may be None since the features part of the + # regular expression is optional + if features is None: + features = GstCapsFeatures() + else: + features = GstCapsFeatures.new_from_str(features) + fields, read = GstStructure._parse_fields(read) + structs.append(GstStructure(name, fields)) + structs.append(features) + return cls(*structs) + + def __repr__(self): + if self.is_any(): + return "GstCaps.new_any()" + write = ["GstCaps("] + first = True + for struct in self.structs: + if first: + first = False + else: + write.append(", ") + write.append(repr(struct[0])) + write.append(", ") + write.append(repr(struct[1])) + write.append(")") + return "".join(write) + + def __str__(self): + """Emulate gst_caps_to_string""" + if self.is_any(): + return "ANY" + if not self.structs: + return "EMPTY" + first = True + write = [] + for struct, features in self.structs: + if first: + first = False + else: + write.append("; ") + write.append(struct._name_to_str()) + if features.is_any or features.features: + # NOTE: is gst_caps_to_string, the feature will not + # be written if it only contains the + # GST_FEATURE_MEMORY_SYSTEM_MEMORY feature, since this + # considered equal to being an empty features. + # We do not seem to require this behaviour + write.append("({!s})".format(features)) + write.append(struct._fields_to_str()) + return "".join(write) + + def append(self, structure, features=None): + """Append a structure with the given features""" + if not isinstance(structure, GstStructure): + wrong_type_for_arg(structure, "GstStructure", "structure") + if features is None: + features = GstCapsFeatures() + if not isinstance(features, GstCapsFeatures): + wrong_type_for_arg( + features, "GstCapsFeatures or None", "features") + self.structs.append((structure, features)) diff --git a/validate/launcher/vfb_server.py b/validate/launcher/vfb_server.py new file mode 100644 index 0000000000..b4b5c768de --- /dev/null +++ b/validate/launcher/vfb_server.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2015,Thibault Saunier +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +import os +import time +from .utils import printc, Colors +from . import loggable +import subprocess + + +class VirtualFrameBufferServer(loggable.Loggable): + def __init__(self, options): + loggable.Loggable.__init__(self) + self.options = options + + def start(self): + raise NotImplementedError + + def stop(self): + raise NotImplementedError + + +class Xvfb(VirtualFrameBufferServer): + + """ Class to run xvfb in a process.""" + + def __init__(self, options): + VirtualFrameBufferServer.__init__(self, options) + self.display_id = ":27" + self._process = None + self._logsfile = None + self._command = "Xvfb %s -screen 0 1920x1080x24" % self.display_id + + def _check_is_up(self, timeout=3, assume_true=True): + """ Check if the xvfb is up, running a simple test based on xset. """ + start = time.time() + while True: + try: + os.environ["DISPLAY"] = self.display_id + subprocess.check_output(["xset", "q"], + stderr=self._logsfile) + return True + except subprocess.CalledProcessError: + pass + except FileNotFoundError: + if assume_true: + print('WARNING: xset not preset on the system,' + ' just wait for %s seconds and hope for the best.' + ' (this is what xvfb-run itself does anyway.)' % timeout) + time.sleep(timeout) + return assume_true + + if time.time() - start > timeout: + return False + + time.sleep(1) + + def start(self): + """ Start xvfb in a subprocess """ + self._logsfile = open(os.path.join(self.options.logsdir, + "xvfb.logs"), 'w+') + if self._check_is_up(assume_true=False): + self.info("xvfb already running") + return (True, None) + + printc("-> Starting xvfb... ", end="") + try: + self.debug("Launching xvfb: %s (logs in %s)", self._command, self._logsfile) + self._process = subprocess.Popen(self._command.split(" "), + stderr=self._logsfile, + stdout=self._logsfile) + self.debug("Launched xvfb") + + # Dirty way to avoid eating to much CPU... + # good enough for us anyway. + time.sleep(1) + + if self._check_is_up(): + printc("OK", Colors.OKGREEN) + return (True, None) + else: + printc("ERROR", Colors.FAIL) + self._process.terminate() + self._process = None + except Exception as ex: + return (False, "Could not launch %s %s\n" + "Make sure Xvfb is installed" % (self._command, ex)) + + def stop(self): + """ Stop the xvfb subprocess if running. """ + if self._process: + self._process.terminate() + self._process = None + self.debug("xvfb stopped") + + +def get_virual_frame_buffer_server(options): + """ + Return a VirtualFrameBufferServer + """ + return Xvfb(options) diff --git a/validate/meson.build b/validate/meson.build new file mode 100644 index 0000000000..84ad240436 --- /dev/null +++ b/validate/meson.build @@ -0,0 +1,31 @@ +inc_dirs = include_directories('.') + +cdata = configuration_data() + +cdata.set('GST_LICENSE', '"LGPL"') +cdata.set('VERSION', '"@0@"'.format(gst_version)) +cdata.set('PACKAGE', '"gst-validate"') +cdata.set('GST_PACKAGE_NAME', '"GStreamer Validate"') +cdata.set('GST_PACKAGE_ORIGIN', '"Unknown package origin"') +cdata.set('GST_API_VERSION', '"@0@"'.format(apiversion)) +cdata.set('VALIDATEPLUGINDIR', '"@0@/@1@/gstreamer-1.0/validate"'.format(get_option('prefix'),get_option('libdir'))) +cdata.set('GST_DATADIR', '"@0@/@1@"'.format(prefix, get_option('datadir'))) +cdata.set('PACKAGE_NAME', '"GStreamer Validate"') +cdata.set('PACKAGE_VERSION', '"@0@"'.format(gst_version)) +if cc.has_header('unistd.h') + cdata.set('HAVE_UNISTD_H', 1) +endif + +configure_file(output : 'config.h', configuration : cdata) + +validate_plugins_install_dir = '@0@/gstreamer-1.0/validate'.format(get_option('libdir')) +subdir('data') +subdir('gst') +subdir('gst-libs') +subdir('launcher') +subdir('tools') +if not get_option('tests').disabled() + subdir('tests') +endif +subdir('plugins') +#subdir('po') diff --git a/validate/plugins/fault_injection/meson.build b/validate/plugins/fault_injection/meson.build new file mode 100644 index 0000000000..196665db81 --- /dev/null +++ b/validate/plugins/fault_injection/meson.build @@ -0,0 +1,11 @@ +if dl.found() + shared_library('gstvalidatefaultinjection', + 'socket_interposer.c', + include_directories : inc_dirs, + link_with: [gstvalidate], + dependencies : [gst_dep, glib_dep, dl], + c_args: ['-DHAVE_CONFIG_H'], + install : true, + install_dir : validate_plugins_install_dir, + ) +endif diff --git a/validate/plugins/fault_injection/socket_interposer.c b/validate/plugins/fault_injection/socket_interposer.c new file mode 100644 index 0000000000..53c1ebb794 --- /dev/null +++ b/validate/plugins/fault_injection/socket_interposer.c @@ -0,0 +1,388 @@ +/* GStreamer + * + * Copyright (C) 2014 YouView TV Ltd + * Authors: Mariusz Buras + * Mathieu Duponchelle + * + * socket_interposer.c : overrides for standard socket functions + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#define _GNU_SOURCE + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "../../gst/validate/gst-validate-scenario.h" + +#if defined(__gnu_linux__) && !defined(__ANDROID__) && !defined (ANDROID) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_CALLBACKS (16) + +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +/* Return 0 to remove the callback immediately */ +typedef int (*socket_interposer_callback) (void *, const void *, size_t); + +struct +{ + socket_interposer_callback callback; + void *userdata; + struct sockaddr_in sockaddr; + int fd; +} callbacks[MAX_CALLBACKS]; + +static int +socket_interposer_remove_callback_unlocked (struct sockaddr_in *addrin, + socket_interposer_callback callback, void *userdata) +{ + size_t i; + for (i = 0; i < MAX_CALLBACKS; i++) { + if (callbacks[i].callback == callback + && callbacks[i].userdata == userdata + && callbacks[i].sockaddr.sin_addr.s_addr == addrin->sin_addr.s_addr + && callbacks[i].sockaddr.sin_port == addrin->sin_port) { + memset (&callbacks[i], 0, sizeof (callbacks[0])); + return 1; + } + } + return 0; +} + +static void +socket_interposer_set_callback (struct sockaddr_in *addrin, + socket_interposer_callback callback, void *userdata) +{ + size_t i; + pthread_mutex_lock (&mutex); + + + socket_interposer_remove_callback_unlocked (addrin, callback, userdata); + for (i = 0; i < MAX_CALLBACKS; i++) { + if (callbacks[i].callback == NULL) { + callbacks[i].callback = callback; + callbacks[i].userdata = userdata; + memcpy (&callbacks[i].sockaddr, addrin, sizeof (struct sockaddr_in)); + callbacks[i].fd = -1; + break; + } + } + pthread_mutex_unlock (&mutex); +} + +int +connect (int socket, const struct sockaddr_in *addrin, socklen_t address_len) +{ + size_t i; + int override_errno = 0; + typedef ssize_t (*real_connect_fn) (int, const struct sockaddr_in *, + socklen_t); + static real_connect_fn real_connect = 0; + ssize_t ret = 0; + + pthread_mutex_lock (&mutex); + + for (i = 0; i < MAX_CALLBACKS; i++) { + if (callbacks[i].sockaddr.sin_addr.s_addr == addrin->sin_addr.s_addr + && callbacks[i].sockaddr.sin_port == addrin->sin_port) { + + callbacks[i].fd = socket; + + if (callbacks[i].callback) { + int ret = callbacks[i].callback (callbacks[i].userdata, NULL, + 0); + if (ret != 0) + override_errno = ret; + else /* Remove the callback */ + memset (&callbacks[i], 0, sizeof (callbacks[0])); + } + + break; + } + } + + pthread_mutex_unlock (&mutex); + + if (!real_connect) { + real_connect = (real_connect_fn) dlsym (RTLD_NEXT, "connect"); + } + + if (!override_errno) { + ret = real_connect (socket, addrin, address_len); + } else { + // override errno + errno = override_errno; + ret = -1; + } + return ret; +} + +ssize_t +send (int socket, const void *buffer, size_t len, int flags) +{ + size_t i; + int override_errno = 0; + typedef ssize_t (*real_send_fn) (int, const void *, size_t, int); + ssize_t ret; + static real_send_fn real_send = 0; + + pthread_mutex_lock (&mutex); + for (i = 0; i < MAX_CALLBACKS; i++) { + if (callbacks[i].fd != 0 && callbacks[i].fd == socket) { + int ret = callbacks[i].callback (callbacks[i].userdata, buffer, + len); + + if (ret != 0) + override_errno = ret; + else /* Remove the callback */ + memset (&callbacks[i], 0, sizeof (callbacks[0])); + + break; + } + } + pthread_mutex_unlock (&mutex); + + if (!real_send) { + real_send = (real_send_fn) dlsym (RTLD_NEXT, "send"); + } + + ret = real_send (socket, buffer, len, flags); + + // override errno + if (override_errno != 0) { + errno = override_errno; + ret = -1; + } + + return ret; + +} + +ssize_t +recv (int socket, void *buffer, size_t length, int flags) +{ + size_t i; + int old_errno; + typedef ssize_t (*real_recv_fn) (int, void *, size_t, int); + ssize_t ret; + static real_recv_fn real_recv = 0; + + if (!real_recv) { + real_recv = (real_recv_fn) dlsym (RTLD_NEXT, "recv"); + } + + ret = real_recv (socket, buffer, length, flags); + old_errno = errno; + + pthread_mutex_lock (&mutex); + for (i = 0; i < MAX_CALLBACKS; i++) { + if (callbacks[i].fd != 0 && callbacks[i].fd == socket) { + int newerrno = callbacks[i].callback (callbacks[i].userdata, buffer, + ret); + + // override errno + if (newerrno != 0) { + old_errno = newerrno; + ret = -1; + } else { /* Remove the callback */ + memset (&callbacks[i], 0, sizeof (callbacks[0])); + } + + break; + } + } + pthread_mutex_unlock (&mutex); + + errno = old_errno; + + return ret; +} + +struct errno_entry +{ + const gchar *str; + int _errno; +}; + +static struct errno_entry errno_map[] = { + {"ECONNABORTED", ECONNABORTED}, + {"ECONNRESET", ECONNRESET}, + {"ENETRESET", ENETRESET}, + {"ECONNREFUSED", ECONNREFUSED}, + {"EHOSTUNREACH", EHOSTUNREACH}, + {"EHOSTDOWN", EHOSTDOWN}, + {NULL, 0}, +}; + +static int +socket_callback_ (GstValidateAction * action, const void *buff, size_t len) +{ + gint times; + gint real_errno; + + gst_structure_get_int (action->structure, "times", ×); + gst_structure_get_int (action->structure, "real_errno", &real_errno); + + times -= 1; + gst_structure_set (action->structure, "times", G_TYPE_INT, times, NULL); + if (times <= 0) { + gst_validate_action_set_done (action); + return 0; + } + + return real_errno; +} + +static gint +errno_string_to_int (const gchar * errno_str) +{ + gint i; + + for (i = 0; errno_map[i]._errno; i += 1) { + if (!g_ascii_strcasecmp (errno_map[i].str, errno_str)) + return errno_map[i]._errno; + } + + return 0; +} + +static gboolean +_fault_injector_loaded (void) +{ + const gchar *ld_preload = g_getenv ("LD_PRELOAD"); + + + return (ld_preload && strstr (ld_preload, "libfaultinjection-1.0.so")); +} + +static gboolean +_execute_corrupt_socket_recv (GstValidateScenario * scenario, + GstValidateAction * action) +{ + struct sockaddr_in addr = + { AF_INET, htons (42), {htonl (INADDR_LOOPBACK)}, {0} }; + gint server_port, times; + const gchar *errno_str; + gint real_errno; + + if (!_fault_injector_loaded ()) { + GST_ERROR + ("The fault injector wasn't preloaded, can't execute socket recv corruption\n" + "You should set LD_PRELOAD to the path of libfaultinjection.so"); + return FALSE; + } + + if (!gst_structure_get_int (action->structure, "port", &server_port)) { + GST_ERROR ("could not get port to corrupt recv on."); + return FALSE; + } + + if (!gst_structure_get_int (action->structure, "times", ×)) { + gst_structure_set (action->structure, "times", G_TYPE_INT, 1, NULL); + } + + errno_str = gst_structure_get_string (action->structure, "errno"); + if (!errno_str) { + GST_ERROR ("Could not get errno string"); + return FALSE; + } + + real_errno = errno_string_to_int (errno_str); + + if (real_errno == 0) { + GST_ERROR ("unrecognized errno"); + return FALSE; + } + + gst_structure_set (action->structure, "real_errno", G_TYPE_INT, real_errno, + NULL); + + addr.sin_port = htons (server_port); + + socket_interposer_set_callback (&addr, + (socket_interposer_callback) socket_callback_, action); + + return GST_VALIDATE_EXECUTE_ACTION_ASYNC; +} + +static gboolean +socket_interposer_init (GstPlugin * plugin) +{ +/* *INDENT-OFF* */ + gst_validate_register_action_type_dynamic (plugin, "corrupt-socket-recv", + GST_RANK_PRIMARY, + _execute_corrupt_socket_recv, ((GstValidateActionParameter[]) { + { + .name = "port", + .description = "The port the socket to be corrupted listens on", + .mandatory = TRUE, + .types = "int", + .possible_variables = NULL, + }, + { + .name = "errno", + .description = "errno to set when failing", + .mandatory = TRUE, + .types = "string", + }, + { + .name = "times", + .description = "Number of times to corrupt recv, default is one", + .mandatory = FALSE, + .types = "int", + .possible_variables = NULL, + .def = "1", + }, + {NULL} + }), + "corrupt the next socket receive", GST_VALIDATE_ACTION_TYPE_ASYNC); +/* *INDENT-ON* */ + + return TRUE; +} + +#else /* No LD_PRELOAD tricks on Windows */ + +static gboolean +socket_interposer_init (GstPlugin * plugin) +{ + return TRUE; +} + +#endif + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + validatefaultinjection, + "Fault injector plugin for GstValidate", + socket_interposer_init, VERSION, "LGPL", GST_PACKAGE_NAME, + GST_PACKAGE_ORIGIN) diff --git a/validate/plugins/gapplication/gstvalidategapplication.c b/validate/plugins/gapplication/gstvalidategapplication.c new file mode 100644 index 0000000000..da8da5787d --- /dev/null +++ b/validate/plugins/gapplication/gstvalidategapplication.c @@ -0,0 +1,80 @@ +/* GStreamer + * + * Copyright (C) 2015 Raspberry Pi Foundation + * Author: Thibault Saunier + * + * gstvalidategapplication.c: GstValidateAction overrides for gapplication + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#define _GNU_SOURCE + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "../../gst/validate/validate.h" +#include "../../gst/validate/gst-validate-scenario.h" +#include "../../gst/validate/gst-validate-utils.h" + + +static gboolean +_execute_stop (GstValidateScenario * scenario, GstValidateAction * action) +{ + g_application_quit (g_application_get_default ()); + + return TRUE; +} + +static gboolean +gst_validate_gapplication_init (GstPlugin * plugin) +{ + GList *config, *tmp; + const gchar *appname; + + config = gst_validate_plugin_get_config (plugin); + + if (!config) + return TRUE; + + for (tmp = config; tmp; tmp = tmp->next) { + appname = gst_structure_get_string (tmp->data, "application-name"); + } + + if (appname && g_strcmp0 (g_get_prgname (), appname)) { + GST_INFO_OBJECT (plugin, "App: %s is not %s", g_get_prgname (), appname); + return TRUE; + } + + gst_validate_register_action_type_dynamic (plugin, "stop", + GST_RANK_PRIMARY, _execute_stop, NULL, + "Sets the pipeline state to NULL", + GST_VALIDATE_ACTION_TYPE_NO_EXECUTION_NOT_FATAL | + GST_VALIDATE_ACTION_TYPE_DOESNT_NEED_PIPELINE); + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + validategapplication, + "GstValidate plugin to run validate on gapplication", + gst_validate_gapplication_init, VERSION, "LGPL", GST_PACKAGE_NAME, + GST_PACKAGE_ORIGIN) diff --git a/validate/plugins/gapplication/meson.build b/validate/plugins/gapplication/meson.build new file mode 100644 index 0000000000..943d0a188a --- /dev/null +++ b/validate/plugins/gapplication/meson.build @@ -0,0 +1,9 @@ +shared_library('gstvalidategapplication', + 'gstvalidategapplication.c', + include_directories : inc_dirs, + c_args: ['-DHAVE_CONFIG_H'], + install: true, + install_dir: validate_plugins_install_dir, + dependencies : [gst_dep, glib_dep, gst_pbutils_dep, gio_dep], + link_with : [gstvalidate] + ) diff --git a/validate/plugins/gtk/gstvalidategtk.c b/validate/plugins/gtk/gstvalidategtk.c new file mode 100644 index 0000000000..9d5d049434 --- /dev/null +++ b/validate/plugins/gtk/gstvalidategtk.c @@ -0,0 +1,544 @@ +/* GStreamer + * + * Copyright (C) 2015 Raspberry Pi Foundation + * Author: Thibault Saunier + * + * gstvalidategtk.c: GstValidateActionTypes to use with gtk applications + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#define _GNU_SOURCE + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "../../gst/validate/gst-validate-report.h" +#include "../../gst/validate/gst-validate-reporter.h" +#include "../../gst/validate/validate.h" +#include "../../gst/validate/gst-validate-scenario.h" +#include "../../gst/validate/gst-validate-utils.h" + +#define ACTION_GDKEVENTS_QUARK g_quark_from_static_string("ACTION_GDKEVENTS_QUARK") +static GList *awaited_actions = NULL; /* A list of GstValidateAction to be executed */ + +static const gchar * +get_widget_name (GtkWidget * widget) +{ + const gchar *name = NULL; + + if (GTK_IS_BUILDABLE (widget)) + name = gtk_buildable_get_name (GTK_BUILDABLE (widget)); + + if (!name) { + name = gtk_widget_get_name (widget); + } + + return name; +} + +static GdkEventType +get_event_type (GstValidateScenario * scenario, GstValidateAction * action) +{ + guint type; + const gchar *etype_str = gst_structure_get_string (action->structure, "type"); + + if (!etype_str) + return GDK_NOTHING; + + if (gst_validate_utils_enum_from_str (GDK_TYPE_EVENT_TYPE, etype_str, &type)) + return type; + + GST_VALIDATE_REPORT (scenario, + g_quark_from_static_string ("scenario::execution-error"), + "Uknown event type %s, the string should look like the ones defined in " + "gdk_event_type_get_type", etype_str); + + return -2; +} + +#if ! GTK_CHECK_VERSION(3,20,0) +static GdkDevice * +get_device (GstValidateAction * action, GdkInputSource input_source) +{ + GList *tmp, *devices; + GdkDevice *device = NULL; + GdkDeviceManager *dev_manager; + + dev_manager = gdk_display_get_device_manager (gdk_display_get_default ()); + devices = + gdk_device_manager_list_devices (dev_manager, GDK_DEVICE_TYPE_MASTER); + + for (tmp = devices; tmp; tmp = tmp->next) { + if (gdk_device_get_source (tmp->data) == input_source) { + device = tmp->data; + break; + } + } + + g_list_free (devices); + + return device; +} +#endif + +static GdkEvent * +_create_key_event (GdkWindow * window, GdkEventType etype, guint keyval, + guint hw_keycode, guint state, GdkDevice * device) +{ + GdkEvent *event = gdk_event_new (etype); + GdkEventKey *kevent = (GdkEventKey *) event; + + kevent->window = g_object_ref (window); + kevent->send_event = TRUE; + kevent->time = GDK_CURRENT_TIME; + kevent->keyval = keyval; + kevent->hardware_keycode = hw_keycode; + kevent->state = state; + + gdk_event_set_device (event, device); + + return event; +} + +static GList * +_create_keyboard_events (GstValidateAction * action, + GdkWindow * window, const gchar * keyname, const gchar * string, + GdkEventType etype) +{ + guint *keys; +#if GTK_CHECK_VERSION(3,20,0) + GdkDisplay *display; + GdkSeat *seat; +#endif + GList *events = NULL; + GdkDevice *device = NULL; + GstValidateScenario *scenario = gst_validate_action_get_scenario (action); + + if (etype == GDK_NOTHING) { + etype = GDK_KEY_PRESS; + } else if (etype != GDK_KEY_PRESS && etype != GDK_KEY_RELEASE) { + GST_VALIDATE_REPORT (scenario, + g_quark_from_static_string ("scenario::execution-error"), + "GdkEvent type %s does not work with the 'keys' parameter", + gst_structure_get_string (action->structure, "type")); + + goto fail; + } +#if GTK_CHECK_VERSION(3,20,0) + display = gdk_display_get_default (); + if (display == NULL) { + GST_VALIDATE_REPORT (scenario, + g_quark_from_static_string ("scenario::execution-error"), + "Could not find a display"); + + goto fail; + } + + seat = gdk_display_get_default_seat (display); + device = gdk_seat_get_keyboard (seat); +#else + device = get_device (action, GDK_SOURCE_KEYBOARD); +#endif + if (device == NULL) { + GST_VALIDATE_REPORT (scenario, + g_quark_from_static_string ("scenario::execution-error"), + "Could not find a keyboard device"); + + goto fail; + } + + if (keyname) { + guint keyval, state; + + gtk_accelerator_parse_with_keycode (keyname, &keyval, &keys, &state); + events = + g_list_append (events, _create_key_event (window, etype, keyval, + keys ? keys[0] : 0, state, device)); + } else if (string) { + gint i; + + for (i = 0; string[i]; i++) { + gint n_keys; + GdkKeymapKey *kmaps; + guint keyval = gdk_unicode_to_keyval (string[i]); + + gdk_keymap_get_entries_for_keyval (gdk_keymap_get_for_display + (gdk_display_get_default ()), keyval, &kmaps, &n_keys); + + events = + g_list_append (events, _create_key_event (window, etype, keyval, + kmaps[0].keycode, 0, device)); + } + } + + gst_object_unref (scenario); + return events; + +fail: + gst_object_unref (scenario); + + return NULL; +} + +typedef struct +{ + gchar **widget_paths; + gint current_index; + GtkWidget *widget; + gboolean found; +} WidgetNameWidget; + +static GtkWidget *_find_widget (GtkContainer * container, + WidgetNameWidget * res); + +static gboolean +_widget_has_name (GtkWidget * widget, gchar * name) +{ + if (g_strcmp0 (get_widget_name (GTK_WIDGET (widget)), name) == 0) { + return TRUE; + } + + return FALSE; +} + +static void +_find_widget_cb (GtkWidget * child, WidgetNameWidget * res) +{ + if (res->found) { + return; + } + + if (_widget_has_name (child, res->widget_paths[res->current_index])) { + res->current_index++; + + if (res->widget_paths[res->current_index] == NULL) { + res->widget = child; + res->found = TRUE; + GST_ERROR ("%p GOT IT!!! %s", child, + gtk_buildable_get_name (GTK_BUILDABLE (child))); + } else if (GTK_CONTAINER (child)) { + res->widget = _find_widget (GTK_CONTAINER (child), res); + } + + } else { + if (GTK_IS_CONTAINER (child)) { + res->widget = _find_widget (GTK_CONTAINER (child), res); + } + } + +} + +static GtkWidget * +_find_widget (GtkContainer * container, WidgetNameWidget * res) +{ + if (res->found) + return res->widget; + + if (_widget_has_name (GTK_WIDGET (container), + res->widget_paths[res->current_index])) { + res->current_index++; + + if (res->widget_paths[res->current_index] == NULL) + return GTK_WIDGET (container); + } + + gtk_container_forall (container, (GtkCallback) _find_widget_cb, res); + + if (res->widget) { + res->current_index++; + + if (res->widget_paths[res->current_index + 1] == NULL) + return res->widget; + + if (GTK_IS_CONTAINER (res->widget)) + _find_widget (GTK_CONTAINER (res->widget), res); + } + + return res->widget; +} + + +static void +_find_button (GtkWidget * widget, GtkWidget ** button) +{ + if (GTK_IS_BUTTON (widget)) + *button = widget; +} + +/* Copy pasted from gtk+/gtk/gtktestutils.c */ +static GSList * +test_find_widget_input_windows (GtkWidget * widget, gboolean input_only) +{ + GdkWindow *window; + GList *node, *children; + GSList *matches = NULL; + gpointer udata; + + window = gtk_widget_get_window (widget); + + gdk_window_get_user_data (window, &udata); + if (udata == widget && (!input_only || (GDK_IS_WINDOW (window) + && gdk_window_is_input_only (GDK_WINDOW (window))))) + matches = g_slist_prepend (matches, window); + children = gdk_window_get_children (gtk_widget_get_parent_window (widget)); + for (node = children; node; node = node->next) { + gdk_window_get_user_data (node->data, &udata); + if (udata == widget && (!input_only || (GDK_IS_WINDOW (node->data) + && gdk_window_is_input_only (GDK_WINDOW (node->data))))) + matches = g_slist_prepend (matches, node->data); + } + return g_slist_reverse (matches); +} + +static GdkWindow * +widget_get_window (GtkWidget * widget) +{ + GdkWindow *res = NULL; + GSList *iwindows = test_find_widget_input_windows (widget, FALSE); + + if (!iwindows) + iwindows = test_find_widget_input_windows (widget, TRUE); + + if (iwindows) + res = iwindows->data; + + g_slist_free (iwindows); + + return res; +} + +static GdkWindow * +get_window (GstValidateScenario * scenario, GstValidateAction * action, + const gchar * widget_name) +{ + GList *tmptoplevel; + GdkWindow *res = NULL; + gchar **widget_paths = NULL; + + GList *toplevels = gtk_window_list_toplevels (); + + if (!widget_name) + widget_name = gst_structure_get_string (action->structure, "widget-name"); + + if (!toplevels) { + GST_VALIDATE_REPORT (scenario, + g_quark_from_static_string ("scenario::execution-error"), + "No Gtk topelevel window found, can not sent GdkEvent"); + + return NULL; + } + + if (!widget_name) { + res = gtk_widget_get_window (toplevels->data); + + goto done; + } + + widget_paths = g_strsplit (widget_name, "/", -1); + + for (tmptoplevel = toplevels; tmptoplevel; tmptoplevel = tmptoplevel->next) { + GtkWidget *widget; + WidgetNameWidget wn; + + wn.widget_paths = widget_paths; + wn.current_index = 0; + wn.found = FALSE; + wn.widget = NULL; + + widget = _find_widget (tmptoplevel->data, &wn); + if (widget) { + if (GTK_IS_TOOL_BUTTON (widget)) { + GST_ERROR ("IS TOOL BUTTON"); + gtk_container_forall (GTK_CONTAINER (widget), + (GtkCallback) _find_button, &widget); + } + + res = widget_get_window (widget); + break; + } + } + +done: + g_list_free (toplevels); + + return res; +} + +static GstValidateActionReturn +_put_events (GstValidateAction * action, GList * events) +{ + GList *tmp; + + if (events == NULL) + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + + gst_mini_object_set_qdata (GST_MINI_OBJECT (action), ACTION_GDKEVENTS_QUARK, + events, NULL); + awaited_actions = g_list_append (awaited_actions, action); + + for (tmp = events; tmp; tmp = tmp->next) { + gdk_event_put (tmp->data); + } + + return GST_VALIDATE_EXECUTE_ACTION_ASYNC; +} + +static gboolean +_execute_put_events (GstValidateScenario * scenario, GstValidateAction * action) +{ + GdkEventType etype; + const gchar *keys, *string; + + GList *events = NULL; + GdkWindow *window = get_window (scenario, action, NULL); + + if (!window) + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + + etype = get_event_type (scenario, action); + if (etype == -2) + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + + keys = gst_structure_get_string (action->structure, "keys"); + string = gst_structure_get_string (action->structure, "string"); + if (keys || string) { + events = _create_keyboard_events (action, window, keys, string, etype); + + return _put_events (action, events); + } + + GST_VALIDATE_REPORT (scenario, + g_quark_from_static_string ("scenario::execution-error"), + "Action parameters not supported yet"); + + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; +} + +static void +_process_event (GdkEvent * event, gpointer data) +{ + GList *tmp; + GdkEvent *done_event = NULL; + GstValidateAction *action = NULL; + + for (tmp = awaited_actions; tmp; tmp = tmp->next) { + GstValidateAction *tmp_action = tmp->data; + GdkEvent *awaited_event = + ((GList *) gst_mini_object_get_qdata (GST_MINI_OBJECT (tmp_action), + ACTION_GDKEVENTS_QUARK))->data; + + if (awaited_event->type == event->type + && ((GdkEventAny *) event)->window == + ((GdkEventAny *) awaited_event)->window) { + + switch (awaited_event->type) { + case GDK_KEY_PRESS: + case GDK_KEY_RELEASE: + if (event->key.keyval == awaited_event->key.keyval) { + done_event = awaited_event; + action = tmp_action; + } + break; + default: + g_assert_not_reached (); + } + } + } + + if (done_event) { + GList *awaited_events = gst_mini_object_get_qdata (GST_MINI_OBJECT (action), + ACTION_GDKEVENTS_QUARK); + + awaited_events = g_list_remove (awaited_events, done_event); + gdk_event_free (done_event); + gst_mini_object_set_qdata (GST_MINI_OBJECT (action), ACTION_GDKEVENTS_QUARK, + awaited_events, NULL); + + if (awaited_events == NULL) { + awaited_actions = g_list_remove (awaited_actions, action); + gst_validate_action_set_done (action); + } + } + + gtk_main_do_event (event); +} + +static gboolean +gst_validate_gtk_init (GstPlugin * plugin) +{ + gdk_event_handler_set (_process_event, NULL, NULL); + +/* *INDENT-OFF* */ + gst_validate_register_action_type_dynamic (plugin, "gtk-put-event", + GST_RANK_PRIMARY, _execute_put_events, ((GstValidateActionParameter[]) { + { + .name = "keys", + .description = "The keyboard keys to be used for the event, parsed" + " with gtk_accelerator_parse_with_keycode, so refer to its documentation" + " for more information", + .mandatory = FALSE, + .types = "string", + .possible_variables = NULL, + }, + { + .name = "string", + .description = "The string to be 'written' by the keyboard" + " sending KEY_PRESS GdkEvents", + .mandatory = FALSE, + .types = "string", + .possible_variables = NULL, + }, + { + .name = "type", + .description = "The event type to get executed. " + "the string should look like the ones in GdkEventType but without" + " the leading 'GDK_'. It is not mandatory as it can be computed from" + " other present fields (e.g, an action with 'keys' will consider the type" + " as 'key_pressed' by default).", + .mandatory = FALSE, + .types = "string", + }, + { + .name = "widget-name", + .description = "The name of the target GdkWidget of the GdkEvent" + ". That widget has to contain a GdkWindow. If not specified," + " the event will be sent to the first toplevel window", + .mandatory = FALSE, + .types = "string", + .possible_variables = NULL, + }, + {NULL} + }), + "Put a GdkEvent on the event list using gdk_put_event", + GST_VALIDATE_ACTION_TYPE_NO_EXECUTION_NOT_FATAL | + GST_VALIDATE_ACTION_TYPE_DOESNT_NEED_PIPELINE); +/* *INDENT-ON* */ + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + validategtk, + "GstValidate plugin to execute action specific to the Gtk toolkit", + gst_validate_gtk_init, VERSION, "LGPL", GST_PACKAGE_NAME, + GST_PACKAGE_ORIGIN) diff --git a/validate/plugins/gtk/meson.build b/validate/plugins/gtk/meson.build new file mode 100644 index 0000000000..dcb7896cb6 --- /dev/null +++ b/validate/plugins/gtk/meson.build @@ -0,0 +1,7 @@ +shared_library('gstvalidategtk', 'gstvalidategtk.c', + include_directories : inc_dirs, + dependencies : [gst_dep, glib_dep, gst_pbutils_dep, gtk_dep], + c_args: ['-DHAVE_CONFIG_H'], + install : true, + install_dir : validate_plugins_install_dir, + link_with : [gstvalidate]) diff --git a/validate/plugins/meson.build b/validate/plugins/meson.build new file mode 100644 index 0000000000..130faef95c --- /dev/null +++ b/validate/plugins/meson.build @@ -0,0 +1,7 @@ +subdir('fault_injection') +subdir('gapplication') +subdir('ssim') + +if gtk_dep.found() + subdir('gtk') +endif diff --git a/validate/plugins/ssim/gstvalidatessim.c b/validate/plugins/ssim/gstvalidatessim.c new file mode 100644 index 0000000000..afad4e0d1d --- /dev/null +++ b/validate/plugins/ssim/gstvalidatessim.c @@ -0,0 +1,817 @@ +/* GStreamer + * + * Copyright (C) 2015 Raspberry Pi Foundation + * Author: Thibault Saunier + * + * gstvalidatessim.c: GstValidateActionTypes to use with ssim applications + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#define _GNU_SOURCE + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include + +#include "../../gst-libs/gst/video/gstvalidatessim.h" +#include "../../gst/validate/gst-validate-report.h" +#include "../../gst/validate/gst-validate-pad-monitor.h" +#include "../../gst/validate/gst-validate-reporter.h" +#include "../../gst/validate/validate.h" +#include "../../gst/validate/gst-validate-scenario.h" +#include "../../gst/validate/gst-validate-utils.h" + +#define SSIM_WRONG_FORMAT g_quark_from_static_string ("validatessim::wrong-format") +#define SSIM_CONVERSION_ERROR g_quark_from_static_string ("validatessim::conversion-error") +#define SSIM_SAVING_ERROR g_quark_from_static_string ("validatessim::saving-error") +#define MONITOR_DATA g_quark_from_static_string ("validate-ssim-monitor-data") +#define NOT_ATTACHED g_quark_from_static_string ("validatessim::not-attached") + +typedef struct _ValidateSsimOverridePrivate ValidateSsimOverridePrivate; + +typedef struct +{ + GstValidateOverride parent; + + ValidateSsimOverridePrivate *priv; + +} ValidateSsimOverride; + +typedef struct +{ + GstValidateOverrideClass parent; + +} ValidateSsimOverrideClass; + +typedef struct +{ + gchar *path; + GstClockTime position; + guint width, height; +} Frame; + +static void +free_frame (Frame * frame) +{ + g_free (frame->path); +} + +struct _ValidateSsimOverridePrivate +{ + gchar *outdir; + gchar *result_outdir; + GstStructure *config; + + gboolean is_attached; + + GstVideoConverter *converter; + GstCaps *last_caps; + GstVideoInfo in_info; + GstVideoInfo out_info; + + GArray *frames; + GstClockTime recurrence; + GstClockTime last_dump_position; + + /* Always used in the streaming thread */ + gboolean needs_reconfigure; + GstVideoFormat save_format; + const gchar *ext; + GstVideoFormat ref_format; + const gchar *ref_ext; +}; + + + +static GType validate_ssim_override_get_type (void); + +#define VALIDATE_SSIM_OVERRIDE_TYPE (validate_ssim_override_get_type ()) +#define VALIDATE_SSIM_OVERRIDE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), VALIDATE_SSIM_OVERRIDE_TYPE, ValidateSsimOverride)) +#define VALIDATE_SSIM_OVERRIDE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), VALIDATE_SSIM_OVERRIDE_TYPE, ValidateSsimOverrideClass)) +#define IS_VALIDATE_SSIM_OVERRIDE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), VALIDATE_SSIM_OVERRIDE_TYPE)) +#define IS_VALIDATE_SSIM_OVERRIDE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), VALIDATE_SSIM_OVERRIDE_TYPE)) +#define VALIDATE_SSIM_OVERRIDE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), VALIDATE_SSIM_OVERRIDE_TYPE, ValidateSsimOverrideClass)) + +/* *INDENT-OFF* */ +G_DEFINE_TYPE_WITH_PRIVATE (ValidateSsimOverride, validate_ssim_override, + GST_TYPE_VALIDATE_OVERRIDE) +/* *INDENT-ON* */ + +static void +runner_stopping (GstValidateRunner * runner, ValidateSsimOverride * self) +{ + GstValidateSsim *ssim; + + guint i, nfiles; + gfloat mssim = 0, lowest = 1, highest = -1, total_avg = 0; + gint npassed = 0, nfailures = 0; + gdouble min_avg_similarity = 0.95, min_lowest_similarity = -1.0, + min_avg = 1.0, min_min = 1.0; + const gchar *compared_files_dir = + gst_structure_get_string (self->priv->config, + "reference-images-dir"); + gint fps_n = 0, fps_d = 1; + + if (!self->priv->is_attached) { + gchar *config_str = gst_structure_to_string (self->priv->config); + GST_VALIDATE_REPORT (self, NOT_ATTACHED, + "The test ended without SSIM being attached for config %s", config_str); + g_free (config_str); + return; + } + + if (!compared_files_dir) { + return; + } + + gst_validate_printf (self, + "Running frame comparison between images from '%s' and '%s' %s%s.\n", + compared_files_dir, self->priv->outdir, + self->priv->result_outdir ? ". Issues can be visialized in " : + " (set 'result-output-dir' in the config file to visualize the result)", + self->priv->result_outdir ? self->priv->result_outdir : ""); + + gst_structure_get_double (self->priv->config, "min-avg-priority", + &min_avg_similarity); + gst_structure_get_double (self->priv->config, "min-lowest-priority", + &min_lowest_similarity); + + gst_structure_get_fraction (self->priv->config, "framerate", &fps_n, &fps_d); + ssim = + gst_validate_ssim_new (runner, min_avg_similarity, min_lowest_similarity, + fps_n, fps_d); + + nfiles = self->priv->frames->len; + for (i = 0; i < nfiles; i++) { + Frame *frame = &g_array_index (self->priv->frames, Frame, i); + gchar *refname, *ref_path, *bname = g_path_get_basename (frame->path); + + if (self->priv->ref_format == GST_VIDEO_FORMAT_ENCODED) + refname = g_strdup_printf ("*.%s", self->priv->ref_ext); + else + refname = g_strdup_printf ("*.%dx%d.%s", frame->width, frame->height, + self->priv->ref_ext); + + ref_path = g_build_path (G_DIR_SEPARATOR_S, compared_files_dir, + refname, NULL); + + if (!gst_validate_ssim_compare_image_files (ssim, ref_path, frame->path, + &mssim, &lowest, &highest, self->priv->result_outdir)) + nfailures++; + else + npassed++; + + min_avg = MIN (min_avg, mssim); + min_min = MIN (lowest, min_min); + total_avg += mssim; + gst_validate_print_position (frame->position, GST_CLOCK_TIME_NONE, 1.0, + g_strdup_printf (" %d / %d avg: %f min: %f (Passed: %d failed: %d)", + i + 1, nfiles, mssim, lowest, npassed, nfailures)); + g_free (bname); + } + + gst_validate_printf (NULL, + "\nAverage similarity: %f, min_avg: %f, min_min: %f\n", + total_avg / nfiles, min_avg, min_min); +} + +static void +_runner_set (GObject * object, GParamSpec * pspec, gpointer user_data) +{ + ValidateSsimOverride *self = VALIDATE_SSIM_OVERRIDE (object); + GstValidateRunner *runner = + gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (self)); + + g_signal_connect (runner, "stopping", G_CALLBACK (runner_stopping), self); + gst_object_unref (runner); +} + +static ValidateSsimOverride * +validate_ssim_override_new (GstStructure * config) +{ + const gchar *format; + ValidateSsimOverride *self = g_object_new (VALIDATE_SSIM_OVERRIDE_TYPE, NULL); + + self->priv->outdir = + g_strdup (gst_structure_get_string (config, "output-dir")); + + if (self->priv->outdir == NULL) { + gchar *template = g_build_filename (g_get_tmp_dir (), + "validatessim-XXXXXX", NULL); + self->priv->outdir = g_mkdtemp (template); + } + + if (!g_file_test (self->priv->outdir, G_FILE_TEST_IS_DIR)) { + if (g_mkdir_with_parents (self->priv->outdir, 0755) != 0) { + + GST_ERROR ("Could not create directory %s", self->priv->outdir); + + g_object_unref (self); + + return NULL; + } + } + + self->priv->config = gst_structure_copy (config); + self->priv->result_outdir = + g_strdup (gst_structure_get_string (config, "result-output-dir")); + + format = gst_structure_get_string (config, "output-video-format"); + if (!format) { + self->priv->save_format = GST_VIDEO_FORMAT_ENCODED; + self->priv->ext = "png"; + } else { + self->priv->save_format = gst_video_format_from_string (format); + self->priv->ext = format; + } + + if (self->priv->save_format == GST_VIDEO_FORMAT_UNKNOWN) { + GST_ERROR ("Uknown video format: %s", format); + + gst_object_unref (self); + + return NULL; + } + + format = gst_structure_get_string (config, "reference-video-format"); + if (!format) { + self->priv->ref_ext = "png"; + self->priv->ref_format = GST_VIDEO_FORMAT_ENCODED; + } else { + self->priv->ref_format = gst_video_format_from_string (format); + if (self->priv->ref_format == GST_VIDEO_FORMAT_UNKNOWN) { + GST_ERROR ("Uknown video format: %s", format); + + gst_object_unref (self); + + return NULL; + } + + self->priv->ref_ext = format; + } + + gst_validate_utils_get_clocktime (config, "check-recurrence", + &self->priv->recurrence); + + g_signal_connect (self, "notify::validate-runner", G_CALLBACK (_runner_set), + NULL); + + return self; +} + + +static gboolean +_can_attach (GstValidateOverride * override, GstValidateMonitor * monitor) +{ + guint i; + GstPad *pad = NULL; + GstCaps *template_caps; + GstElement *element = NULL; + GstStructure *structure; + gboolean res = FALSE; + ValidateSsimOverride *self = VALIDATE_SSIM_OVERRIDE (override); + + if (self->priv->is_attached) { + GST_ERROR_OBJECT (override, "Already attached"); + + goto fail; + } + + if (!GST_IS_VALIDATE_PAD_MONITOR (monitor)) { + goto fail; + } + + pad = GST_PAD (gst_validate_monitor_get_target (monitor)); + element = gst_validate_monitor_get_element (monitor); + + if (!element) { + GST_INFO_OBJECT (monitor, "Not in an element yet, can't attach"); + goto fail; + } + + if ((gst_validate_element_has_klass (element, "Converter") || + gst_validate_element_has_klass (element, "Filter") || + gst_validate_element_has_klass (element, "Decoder")) && + GST_PAD_IS_SINK (pad)) { + GST_INFO_OBJECT (override, "Not attaching on filter or decoder sinkpads"); + + goto fail; + } + + if (!GST_PAD_PAD_TEMPLATE (pad)) { + GST_INFO_OBJECT (pad, + "Doesn't have template, can't use it %" GST_PTR_FORMAT, + gst_pad_query_caps (pad, NULL)); + return FALSE; + } + + template_caps = GST_PAD_TEMPLATE_CAPS (GST_PAD_PAD_TEMPLATE (pad)); + if (gst_caps_is_any (template_caps)) { + res = TRUE; + goto done; + } + + for (i = 0; i < gst_caps_get_size (template_caps); i++) { + structure = gst_caps_get_structure (template_caps, i); + if (gst_structure_has_name (structure, "video/x-raw")) { + res = TRUE; + break; + } + } + +done: + if (res) { + gchar *path = gst_object_get_path_string (GST_OBJECT (pad)); + GST_INFO_OBJECT (override, "Wrapping %" GST_PTR_FORMAT, pad); + + gst_validate_reporter_set_name (GST_VALIDATE_REPORTER (override), + g_strdup_printf ("ssim-override-%s", path)); + gst_validate_printf (self, "Using %s as output directory\n", + self->priv->outdir); + g_free (path); + } + + if (pad) + gst_object_unref (pad); + if (element) + gst_object_unref (element); + return res; + +fail: + res = FALSE; + goto done; +} + +static void +validate_ssim_override_attached (GstValidateOverride * override) +{ + ValidateSsimOverride *self = VALIDATE_SSIM_OVERRIDE (override); + + self->priv->is_attached = TRUE; +} + +static void +_finalize (GObject * object) +{ + ValidateSsimOverridePrivate *priv = VALIDATE_SSIM_OVERRIDE (object)->priv; + GDir *outdir_handle = NULL; + const gchar *filename = NULL; + GError *error = NULL; + + if (priv->converter) + gst_video_converter_free (priv->converter); + + if (priv->last_caps) + gst_caps_unref (priv->last_caps); + + if (priv->config && !gst_structure_has_field (priv->config, "output-dir")) { + /* Remove temporary directory contents (expected to be files, no sub-directories). */ + outdir_handle = g_dir_open (priv->outdir, 0, &error); + if (outdir_handle != NULL) { + while ((filename = g_dir_read_name (outdir_handle))) { + gchar *path = + g_build_path (G_DIR_SEPARATOR_S, priv->outdir, filename, NULL); + g_remove (path); + g_free (path); + } + g_dir_close (outdir_handle); + } else { + GST_ERROR ("Unable to cleanup temporary directory %s: %s", priv->outdir, + error->message); + g_error_free (error); + } + g_rmdir (priv->outdir); + } + + g_free (priv->outdir); + g_free (priv->result_outdir); + g_array_unref (priv->frames); + + if (priv->config) + gst_structure_free (priv->config); +} + +static void +validate_ssim_override_class_init (ValidateSsimOverrideClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstValidateOverrideClass *override_class = + GST_VALIDATE_OVERRIDE_CLASS (klass); + + gobject_class->finalize = _finalize; + override_class->attached = validate_ssim_override_attached; + + if (!gst_validate_is_initialized ()) + return; + + GST_VALIDATE_OVERRIDE_CLASS (klass)->can_attach = _can_attach; + + gst_validate_issue_register (gst_validate_issue_new (SSIM_WRONG_FORMAT, + "The ValidateSSim plugin can not work with a video format", + "The GstValidate ssim plugin was not able to work" + " with a video format that flowed in the pipeline." + " Make sure you properly configured the plugin", + GST_VALIDATE_REPORT_LEVEL_CRITICAL)); + + gst_validate_issue_register (gst_validate_issue_new (SSIM_CONVERSION_ERROR, + "The ValidateSSim plugin could not convert a frame in the needed format", + "The GstValidate ssim plugin needs to convert the frame in a colorspace" + " it can handle, but it was not possible.", + GST_VALIDATE_REPORT_LEVEL_CRITICAL)); + + gst_validate_issue_register (gst_validate_issue_new (SSIM_SAVING_ERROR, + "The ValidateSSim plugin could not save PNG file", + "The ValidateSSim plugin could not save PNG file", + GST_VALIDATE_REPORT_LEVEL_CRITICAL)); + + gst_validate_issue_register (gst_validate_issue_new + (NOT_ATTACHED, + "The ssim override was never attached.", + "The ssim override was never attached.", + GST_VALIDATE_REPORT_LEVEL_CRITICAL)); +} + +static void +validate_ssim_override_init (ValidateSsimOverride * self) +{ + self->priv = validate_ssim_override_get_instance_private (self); + + self->priv->needs_reconfigure = TRUE; + self->priv->frames = g_array_new (TRUE, TRUE, sizeof (Frame)); + g_array_set_clear_func (self->priv->frames, (GDestroyNotify) free_frame); +} + +static gboolean +_set_videoconvert (ValidateSsimOverride * o, + GstValidatePadMonitor * pad_monitor) +{ + GstCaps *caps; + GstVideoFormat format; + ValidateSsimOverridePrivate *priv = o->priv; + GstPad *pad = + GST_PAD (gst_validate_monitor_get_target (GST_VALIDATE_MONITOR + (pad_monitor))); + + caps = gst_pad_get_current_caps (pad); + gst_object_unref (pad); + gst_caps_replace (&priv->last_caps, caps); + + gst_video_info_init (&priv->in_info); + gst_video_info_init (&priv->out_info); + if (priv->converter) { + gst_video_converter_free (priv->converter); + priv->converter = NULL; + } + + if (!gst_video_info_from_caps (&priv->in_info, priv->last_caps)) { + GST_VALIDATE_REPORT (o, SSIM_WRONG_FORMAT, + "The format %" GST_PTR_FORMAT " is not supported" + " by the plugin", pad_monitor->last_caps); + + return FALSE; + } + + if (GST_VIDEO_INFO_HAS_ALPHA (&priv->in_info)) + format = GST_VIDEO_FORMAT_BGRA; + else + format = GST_VIDEO_FORMAT_BGRx; + + if (priv->in_info.finfo->format == format) { + GST_INFO_OBJECT (o, "No conversion needed"); + + return TRUE; + } + + if (priv->save_format != GST_VIDEO_FORMAT_ENCODED) + format = priv->save_format; + + gst_video_info_set_format (&priv->out_info, format, + priv->in_info.width, priv->in_info.height); + priv->out_info.fps_d = priv->in_info.fps_d; + priv->out_info.fps_n = priv->in_info.fps_n; + + priv->converter = gst_video_converter_new (&priv->in_info, + &priv->out_info, NULL); + + return TRUE; + +} + +static gboolean +has_frame (ValidateSsimOverride * self, gchar * name) +{ + guint i; + GArray *frames = self->priv->frames; + + for (i = 0; i < frames->len; ++i) { + if (g_strcmp0 (g_array_index (frames, Frame, i).path, name) == 0) + return TRUE; + } + + return FALSE; +} + + +static gchar * +_get_filename (ValidateSsimOverride * self, GstValidatePadMonitor * monitor, + GstClockTime position) +{ + gint i = 0; + gchar *outname = NULL, *s; + + if (self->priv->save_format == GST_VIDEO_FORMAT_ENCODED) + s = g_strdup_printf ("%" GST_VALIDATE_SSIM_TIME_FORMAT ".%s", + GST_TIME_ARGS (position), self->priv->ext); + else + s = g_strdup_printf ("%" GST_VALIDATE_SSIM_TIME_FORMAT ".%dx%d.%s", + GST_TIME_ARGS (position), + self->priv->out_info.width, + self->priv->out_info.height, self->priv->ext); + + outname = g_build_path (G_DIR_SEPARATOR_S, self->priv->outdir, s, NULL); + + g_free (s); + while (has_frame (self, outname)) { + g_free (outname); + if (self->priv->save_format == GST_VIDEO_FORMAT_ENCODED) + s = g_strdup_printf ("%" GST_VALIDATE_SSIM_TIME_FORMAT "-%d.%s", + GST_TIME_ARGS (position), i++, self->priv->ext); + else + s = g_strdup_printf ("%" GST_VALIDATE_SSIM_TIME_FORMAT "-%d.%dx%d.%s", + GST_TIME_ARGS (position), i++, self->priv->out_info.width, + self->priv->out_info.height, self->priv->ext); + + outname = g_build_path (G_DIR_SEPARATOR_S, self->priv->outdir, s, NULL); + g_free (s); + } + + return outname; +} + +static gboolean +_should_dump_buffer (ValidateSsimOverride * self, + GstValidatePadMonitor * pad_monitor, GstClockTime position) +{ + ValidateSsimOverridePrivate *priv = self->priv; + + if (!GST_CLOCK_TIME_IS_VALID (priv->recurrence)) + return TRUE; + + if (priv->needs_reconfigure) + return TRUE; + + /* recurence 0 means, dump exclusively on reconfiguration */ + if (priv->recurrence == 0) + return FALSE; + + if (position > priv->last_dump_position ? + position - priv->last_dump_position : 0 >= priv->recurrence) + return TRUE; + + return FALSE; +} + +static gboolean +_save_frame (ValidateSsimOverride * self, GstVideoFrame * frame, + const gchar * outname) +{ + gboolean res = TRUE; + cairo_status_t status; + cairo_surface_t *surface; + GError *error = NULL; + + if (self->priv->save_format == GST_VIDEO_FORMAT_ENCODED) { + surface = + cairo_image_surface_create_for_data (GST_VIDEO_FRAME_PLANE_DATA (frame, + 0), CAIRO_FORMAT_RGB24, GST_VIDEO_FRAME_WIDTH (frame), + GST_VIDEO_FRAME_HEIGHT (frame), GST_VIDEO_FRAME_PLANE_STRIDE (frame, + 0)); + + if ((status = cairo_surface_write_to_png (surface, outname)) != + CAIRO_STATUS_SUCCESS) { + GST_VALIDATE_REPORT (self, SSIM_SAVING_ERROR, + "Could not save '%s', cairo status is '%s'", outname, + cairo_status_to_string (status)); + + res = FALSE; + } + + cairo_surface_destroy (surface); + + return res; + } + + if (!g_file_set_contents (outname, + GST_VIDEO_FRAME_PLANE_DATA (frame, 0), + GST_VIDEO_FRAME_SIZE (frame), &error)) { + GST_VALIDATE_REPORT (self, SSIM_SAVING_ERROR, + "Could not save %s error: %s", outname, error->message); + res = FALSE; + } + + return res; +} + +static void +_handle_buffer (GstValidateOverride * override, + GstValidatePadMonitor * pad_monitor, GstBuffer * buffer) +{ + gchar *outname = NULL; + GstVideoFrame frame; + Frame iframe; + + ValidateSsimOverride *o = VALIDATE_SSIM_OVERRIDE (override); + ValidateSsimOverridePrivate *priv = o->priv; + + GstClockTime position; + + position = gst_segment_to_stream_time (&pad_monitor->segment, + GST_FORMAT_TIME, GST_BUFFER_PTS (buffer)); + + if (!_should_dump_buffer (o, pad_monitor, position)) { + GST_LOG_OBJECT (override, "Not dumping buffer: %" GST_TIME_FORMAT, + GST_TIME_ARGS (position)); + + return; + } + + if (priv->needs_reconfigure) { + priv->needs_reconfigure = !_set_videoconvert (o, pad_monitor); + } + + if (priv->converter) { + GstVideoFrame inframe; + GstBuffer *outbuf; + + if (!gst_video_frame_map (&inframe, &priv->in_info, buffer, GST_MAP_READ)) { + GST_VALIDATE_REPORT (o, SSIM_CONVERSION_ERROR, + "Could not map the videoframe %p", buffer); + + return; + } + + outbuf = gst_buffer_new_allocate (NULL, priv->out_info.size, NULL); + if (!gst_video_frame_map (&frame, &priv->out_info, outbuf, GST_MAP_WRITE)) { + GST_VALIDATE_REPORT (o, SSIM_CONVERSION_ERROR, + "Could not map the outbuffer %p", outbuf); + + gst_buffer_unref (outbuf); + return; + } + gst_buffer_unref (outbuf); + gst_video_converter_frame (priv->converter, &inframe, &frame); + gst_video_frame_unmap (&inframe); + } else { + if (!gst_video_frame_map (&frame, &priv->in_info, buffer, GST_MAP_WRITE)) { + GST_VALIDATE_REPORT (o, SSIM_CONVERSION_ERROR, + "Could not map the buffer %p", buffer); + return; + } + } + + outname = _get_filename (o, pad_monitor, position); + if (_save_frame (o, &frame, outname)) { + priv->last_dump_position = position; + + iframe.position = position; + iframe.path = outname; + iframe.width = priv->in_info.width; + iframe.height = priv->in_info.height; + g_array_append_val (priv->frames, iframe); + } + + gst_video_frame_unmap (&frame); +} + +static void +_handle_event (GstValidateOverride * override, + GstValidateMonitor * pad_monitor, GstEvent * event) +{ + ValidateSsimOverride *self = VALIDATE_SSIM_OVERRIDE (override); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_STOP: + self->priv->needs_reconfigure = TRUE; + break; + case GST_EVENT_CAPS: + self->priv->needs_reconfigure = TRUE; + break; + default: + break; + } +} + +static gboolean +_map_confg (GQuark field_id, GValue * value, GstStructure * structure) +{ + if (!gst_structure_id_has_field (structure, field_id)) + gst_structure_id_set_value (structure, field_id, value); + + return TRUE; +} + +static gboolean +gst_validate_ssim_init (GstPlugin * plugin) +{ + GList *tmp, *config; + GstStructure *config_structure = NULL; + + if (!gst_validate_is_initialized ()) + return FALSE; + + config = gst_validate_plugin_get_config (plugin); + for (tmp = config; tmp; tmp = tmp->next) { + gboolean is_config; + + if (gst_structure_get_boolean (tmp->data, "is-config", &is_config)) { + if (is_config) { + config_structure = tmp->data; + break; + } + } + } + + for (tmp = config; tmp; tmp = tmp->next) { + const gchar *name = gst_structure_get_string (tmp->data, "element-name"); + const gchar *target_element_classification = + gst_structure_get_string (tmp->data, "element-classification"); + + if (tmp->data == config_structure) + continue; + + if (config_structure) { + gst_structure_map_in_place (config_structure, + (GstStructureMapFunc) _map_confg, tmp->data); + } + if ((name || target_element_classification)) { + GstValidateOverride *override = + GST_VALIDATE_OVERRIDE (validate_ssim_override_new (tmp->data)); + + if (override == NULL) { + GST_ERROR ("Could not create override with config %" + GST_PTR_FORMAT, tmp->data); + + continue; + } + + override->buffer_probe_handler = + (GstValidateOverrideBufferHandler) _handle_buffer; + override->buffer_handler = + (GstValidateOverrideBufferHandler) _handle_buffer; + override->event_handler = (GstValidateOverrideEventHandler) _handle_event; + + if (target_element_classification) + gst_validate_override_register_by_klass (target_element_classification, + override); + else if (name) + gst_validate_override_register_by_name (name, override); + else + g_assert_not_reached (); + + g_object_unref (override); + } else { + GST_ERROR ("Wrong configuration '%" GST_PTR_FORMAT + "'element-classification' and output-dir are mandatory fields", + tmp->data); + g_assert_not_reached (); + } + } + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + validatessim, + "GstValidate plugin to run the ssim algorithm on raw" + " video buffers. It allows you to generate png files" + "\n " + " and then check them against pre generated, reference images." + "\n " + " The following parameters can be passed in the configuration file:" + "\n " + " 'element-classification': The target element classification as define in gst_element_class_set_metadata" + "\n " + " 'output-dir': The directory in which the image files will be saved'" + "\n", + gst_validate_ssim_init, VERSION, "LGPL", GST_PACKAGE_NAME, + GST_PACKAGE_ORIGIN) diff --git a/validate/plugins/ssim/meson.build b/validate/plugins/ssim/meson.build new file mode 100644 index 0000000000..5562e41e2e --- /dev/null +++ b/validate/plugins/ssim/meson.build @@ -0,0 +1,12 @@ +if validate_video_dep.found() + shared_library('gstvalidatessim', + 'gstvalidatessim.c', + include_directories : inc_dirs, + link_with: [gstvalidate], + dependencies : [gst_dep, gst_video_dep, glib_dep, validate_video_dep, + gst_pbutils_dep], + install : true, + install_dir : validate_plugins_install_dir, + c_args: ['-DHAVE_CONFIG_H'], + ) +endif diff --git a/validate/tests/check/meson.build b/validate/tests/check/meson.build new file mode 100644 index 0000000000..c70c556f3d --- /dev/null +++ b/validate/tests/check/meson.build @@ -0,0 +1,49 @@ +# tests and condition when to skip the test +validate_tests = [ + ['validate/padmonitor'], + ['validate/monitoring'], + ['validate/reporting'], + ['validate/overrides'], + ['validate/scenario'], + ['validate/utilities'], + ['validate/expression_parser'], +] + +test_defines = [ + '-UG_DISABLE_ASSERT', + '-UG_DISABLE_CAST_CHECKS', + '-DGST_CHECK_TEST_ENVIRONMENT_BEACON="GST_STATE_IGNORE_ELEMENTS"', + '-DTESTFILE="' + meson.current_source_dir() + '/meson.build"', + '-DGST_USE_UNSTABLE_API', +] + +foreach t : validate_tests + fname = '@0@.c'.format(t.get(0)) + test_name = t.get(0).underscorify() + if t.length() == 2 + skip_test = t.get(1) + else + skip_test = false + endif + + if not skip_test + env = environment() + env.set('GST_STATE_IGNORE_ELEMENTS', '') + env.set('GST_PLUGIN_SYSTEM_PATH_1_0', '') + env.set('GST_PLUGIN_PATH_1_0', [meson.build_root()] + pluginsdirs) + env.set('GST_REGISTRY', '@0@/@1@.registry'.format(meson.current_build_dir(), test_name)) + env.set('GST_PLUGIN_SCANNER_1_0', gst_plugin_scanner_path) + + exe = executable(test_name, fname, + 'validate/test-utils.c', + c_args : gst_c_args + test_defines, + include_directories : [inc_dirs], + dependencies : [validate_dep, gst_check_dep], + link_with: gstvalidate + ) + env.set('GST_REGISTRY', + '@0@/@1@.registry'.format(meson.current_build_dir(), test_name)) + test(test_name, exe, env: env) + endif +endforeach + diff --git a/validate/tests/check/validate/expression_parser.c b/validate/tests/check/validate/expression_parser.c new file mode 100644 index 0000000000..182730b95c --- /dev/null +++ b/validate/tests/check/validate/expression_parser.c @@ -0,0 +1,51 @@ +#include +#include +#include +#include + +static int +get_var (const gchar * name, double *value, gpointer udata) +{ + *value = (double) GPOINTER_TO_INT (udata); + + return 1; +} + +GST_START_TEST (test_expression_parser) +{ + fail_unless_equals_float (gst_validate_utils_parse_expression ("10 / 2", NULL, + NULL, NULL), 5.0); + + fail_unless_equals_float (gst_validate_utils_parse_expression ("10 / 0.5", + NULL, NULL, NULL), 20); + + fail_unless_equals_float (gst_validate_utils_parse_expression + ("max(100, (10 / 0.1))", NULL, NULL, NULL), 100); + + fail_unless_equals_float (gst_validate_utils_parse_expression + ("min(10, (duration - 0.1) / 0.1)", get_var, GINT_TO_POINTER (1), NULL), + 9); +} + +GST_END_TEST; + +static Suite * +gst_validate_suite (void) +{ + Suite *s = suite_create ("registry"); + TCase *tc_chain = tcase_create ("registry"); + suite_add_tcase (s, tc_chain); + + if (atexit (gst_validate_deinit) != 0) { + GST_ERROR ("failed to set gst_validate_deinit as exit function"); + } + + g_setenv ("GST_VALIDATE_REPORTING_DETAILS", "all", TRUE); + gst_validate_init (); + tcase_add_test (tc_chain, test_expression_parser); + gst_validate_deinit (); + + return s; +} + +GST_CHECK_MAIN (gst_validate); diff --git a/validate/tests/check/validate/monitoring.c b/validate/tests/check/validate/monitoring.c new file mode 100644 index 0000000000..fd4bc79d34 --- /dev/null +++ b/validate/tests/check/validate/monitoring.c @@ -0,0 +1,111 @@ +/* GstValidate + * Copyright (C) 2014 Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include "test-utils.h" + +GST_START_TEST (monitors_added) +{ + GList *tmp; + GstValidateRunner *runner; + GstValidateMonitor *monitor; + GstElement *pipeline = gst_pipeline_new ("validate-pipeline"); + GstElement *src, *sink; + + src = gst_element_factory_make ("fakesrc", "source"); + sink = gst_element_factory_make ("fakesink", "sink"); + + runner = gst_validate_runner_new (); + fail_unless (GST_IS_VALIDATE_RUNNER (runner)); + + monitor = gst_validate_monitor_factory_create (GST_OBJECT_CAST (pipeline), + runner, NULL); + fail_unless (GST_IS_VALIDATE_BIN_MONITOR (monitor)); + + gst_bin_add_many (GST_BIN (pipeline), src, sink, NULL); + gst_element_link (src, sink); + + /* Check that the elements are properly monitored */ + fail_unless_equals_int (g_list_length (src->srcpads), 1); + for (tmp = src->srcpads; tmp; tmp = tmp->next) + fail_unless (GST_IS_VALIDATE_PAD_MONITOR (g_object_get_data ((GObject *) + tmp->data, "validate-monitor"))); + + fail_unless_equals_int (g_list_length (sink->sinkpads), 1); + for (tmp = sink->sinkpads; tmp; tmp = tmp->next) + fail_unless (GST_IS_VALIDATE_PAD_MONITOR (g_object_get_data ((GObject *) + tmp->data, "validate-monitor"))); + + /* clean up */ + gst_object_unref (pipeline); + gst_object_unref (monitor); + gst_object_unref (runner); +} + +GST_END_TEST; + +GST_START_TEST (monitors_cleanup) +{ + GstElement *src, *sink; + GstValidateMonitor *monitor, *pmonitor1, *pmonitor2; + + GstValidateRunner *runner = gst_validate_runner_new (); + GstElement *pipeline = gst_pipeline_new ("validate-pipeline"); + + src = gst_element_factory_make ("fakesrc", "source"); + sink = gst_element_factory_make ("fakesink", "sink"); + + monitor = gst_validate_monitor_factory_create (GST_OBJECT_CAST (pipeline), + runner, NULL); + gst_bin_add_many (GST_BIN (pipeline), src, sink, NULL); + gst_element_link (src, sink); + + /* Check cleanup */ + pmonitor1 = + g_object_get_data ((GObject *) src->srcpads->data, "validate-monitor"); + pmonitor2 = + g_object_get_data ((GObject *) sink->sinkpads->data, "validate-monitor"); + gst_check_objects_destroyed_on_unref (monitor, pmonitor1, pmonitor2, NULL); + gst_check_objects_destroyed_on_unref (pipeline, src, sink, NULL); +} + +GST_END_TEST; + + +static Suite * +gst_validate_suite (void) +{ + Suite *s = suite_create ("monitoring"); + TCase *tc_chain = tcase_create ("monitoring"); + suite_add_tcase (s, tc_chain); + + if (atexit (gst_validate_deinit) != 0) { + GST_ERROR ("failed to set gst_validate_deinit as exit function"); + } + + tcase_add_test (tc_chain, monitors_added); + tcase_add_test (tc_chain, monitors_cleanup); + + return s; +} + +GST_CHECK_MAIN (gst_validate); diff --git a/validate/tests/check/validate/overrides.c b/validate/tests/check/validate/overrides.c new file mode 100644 index 0000000000..78c2eedacb --- /dev/null +++ b/validate/tests/check/validate/overrides.c @@ -0,0 +1,115 @@ +/* GstValidate + * Copyright (C) 2014 Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include + +static const gchar *some_overrides = + "change-severity, issue-id=buffer::not-expected-one, new-severity=critical\n" + "change-severity, issue-id=buffer::not-expected-one, new-severity=warning, element-factory-name=queue"; + +static void +_check_message_level (GstValidateRunner * runner, + gint previous_reports, const gchar * factoryname, + GstValidateReportLevel level, const gchar * message_id) +{ + GList *reports; + GstElement *element; + GstValidateMonitor *monitor; + + element = gst_element_factory_make (factoryname, NULL); + fail_unless (g_setenv ("GST_VALIDATE_REPORTING_DETAILS", "all", TRUE)); + monitor = + gst_validate_monitor_factory_create (GST_OBJECT (element), runner, NULL); + + GST_VALIDATE_REPORT (monitor, g_quark_from_string (message_id), + "Just some fakery"); + + reports = gst_validate_runner_get_reports (runner); + fail_unless_equals_int (g_list_length (reports), previous_reports + 1); + fail_unless_equals_int (((GstValidateReport *) g_list_nth_data (reports, + previous_reports))->level, level); + g_list_free_full (reports, (GDestroyNotify) gst_validate_report_unref); + gst_object_unref (element); + gst_object_unref (monitor); + +} + +GST_START_TEST (check_text_overrides) +{ + GstValidateIssue *issue; + GstValidateRunner *runner = gst_validate_runner_new (); + gchar *override_filename = + g_strdup_printf ("%s%c%s", g_get_tmp_dir (), G_DIR_SEPARATOR, + "some_overrides"); + + fail_unless (g_file_set_contents (override_filename, + some_overrides, -1, NULL)); + + issue = + gst_validate_issue_from_id (g_quark_from_string + ("buffer::not-expected-one")); + fail_unless (issue != NULL); + + assert_equals_int (issue->default_level, GST_VALIDATE_REPORT_LEVEL_WARNING); + + g_setenv ("GST_VALIDATE_OVERRIDE", override_filename, TRUE); + gst_validate_override_registry_preload (); + assert_equals_int (issue->default_level, GST_VALIDATE_REPORT_LEVEL_CRITICAL); + + /* Check that with a queue, the level of a + * buffer::not-expected-one is WARNING */ + _check_message_level (runner, 0, "queue", GST_VALIDATE_REPORT_LEVEL_WARNING, + "buffer::not-expected-one"); + + /* Check that with an identity, the level of a + * buffer::not-expected-one is CRITICAL */ + _check_message_level (runner, 1, "identity", + GST_VALIDATE_REPORT_LEVEL_CRITICAL, "buffer::not-expected-one"); + + g_remove (override_filename); + g_free (override_filename); + gst_object_unref (runner); +} + +GST_END_TEST; + + +static Suite * +gst_validate_suite (void) +{ + Suite *s = suite_create ("registry"); + TCase *tc_chain = tcase_create ("registry"); + suite_add_tcase (s, tc_chain); + + if (atexit (gst_validate_deinit) != 0) { + GST_ERROR ("failed to set gst_validate_deinit as exit function"); + } + + g_setenv ("GST_VALIDATE_REPORTING_DETAILS", "all", TRUE); + gst_validate_init (); + tcase_add_test (tc_chain, check_text_overrides); + gst_validate_deinit (); + + return s; +} + +GST_CHECK_MAIN (gst_validate); diff --git a/validate/tests/check/validate/padmonitor.c b/validate/tests/check/validate/padmonitor.c new file mode 100644 index 0000000000..677543f231 --- /dev/null +++ b/validate/tests/check/validate/padmonitor.c @@ -0,0 +1,1101 @@ +/* GstValidate + * Copyright (C) 2014 Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include "test-utils.h" + +static GstValidateRunner * +_start_monitoring_bin (GstBin * bin) +{ + GstValidateRunner *runner; + GstValidateMonitor *monitor; + + runner = gst_validate_runner_new (); + monitor = + gst_validate_monitor_factory_create (GST_OBJECT (bin), runner, NULL); + + gst_validate_reporter_set_handle_g_logs (GST_VALIDATE_REPORTER (monitor)); + return runner; +} + +static void +_stop_monitoring_bin (GstBin * bin, GstValidateRunner * runner) +{ + GstValidateMonitor *monitor; + + monitor = + (GstValidateMonitor *) g_object_get_data (G_OBJECT (bin), + "validate-monitor"); + gst_object_unref (bin); + ASSERT_OBJECT_REFCOUNT (monitor, "monitor", 1); + gst_object_unref (monitor); + ASSERT_OBJECT_REFCOUNT (runner, "runner", 2); + gst_object_unref (runner); +} + +static GstValidateMonitor * +_start_monitoring_element (GstElement * element, GstValidateRunner * runner) +{ + GstValidateMonitor *monitor; + + monitor = gst_validate_monitor_factory_create (GST_OBJECT (element), + runner, NULL); + + return monitor; +} + +static void +_check_reports_refcount (GstPad * pad, gint refcount) +{ + GList *tmp, *reports; + GstValidateReporter *reporter = + (GstValidateReporter *) g_object_get_data (G_OBJECT (pad), + "validate-monitor"); + + reports = gst_validate_reporter_get_reports (reporter); + /* We take a ref here */ + refcount += 1; + + for (tmp = reports; tmp; tmp = tmp->next) + fail_unless_equals_int (((GstMiniObject *) tmp->data)->refcount, refcount); + + g_list_free_full (reports, (GDestroyNotify) gst_validate_report_unref); +} + +static GstBuffer * +gst_discont_buffer_new (void) +{ + GstBuffer *buffer = gst_buffer_new (); + + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); + + return buffer; +} + +GST_START_TEST (buffer_before_segment) +{ + GstPad *srcpad, *sinkpad; + GstElement *sink; + GstValidateRunner *runner; + GstValidateReport *report; + GstValidateMonitor *monitor; + GList *reports; + + /* getting an existing element class is cheating, but easier */ + sink = gst_element_factory_make ("fakesink", "fakesink"); + + srcpad = gst_pad_new ("src", GST_PAD_SRC); + sinkpad = gst_element_get_static_pad (sink, "sink"); + fail_unless (gst_pad_link (srcpad, sinkpad) == GST_PAD_LINK_OK); + gst_clear_object (&sinkpad); + + fail_unless (g_setenv ("GST_VALIDATE_REPORTING_DETAILS", "all", TRUE)); + runner = gst_validate_runner_new (); + monitor = + gst_validate_monitor_factory_create (GST_OBJECT (srcpad), runner, NULL); + fail_unless (GST_IS_VALIDATE_PAD_MONITOR (monitor)); + + + /* We want to handle the src behaviour ourself */ + fail_unless (gst_pad_activate_mode (srcpad, GST_PAD_MODE_PUSH, TRUE)); + fail_unless_equals_int (gst_element_set_state (sink, GST_STATE_PLAYING), + GST_STATE_CHANGE_ASYNC); + + /* Send a buffer before pushing any segment (FAILS) */ + { + _gst_check_expecting_log = TRUE; + fail_unless_equals_int (gst_pad_push (srcpad, + gst_discont_buffer_new ()), GST_FLOW_OK); + + reports = gst_validate_runner_get_reports (runner); + assert_equals_int (g_list_length (reports), 1); + report = reports->data; + fail_unless_equals_int (report->level, GST_VALIDATE_REPORT_LEVEL_WARNING); + fail_unless_equals_int (report->issue->issue_id, BUFFER_BEFORE_SEGMENT); + g_list_free_full (reports, (GDestroyNotify) gst_validate_report_unref); + } + + /* Setup all needed event and push a new buffer (WORKS) */ + { + _gst_check_expecting_log = FALSE; + gst_check_setup_events (srcpad, sink, NULL, GST_FORMAT_TIME); + fail_unless_equals_int (gst_pad_push (srcpad, gst_discont_buffer_new ()), + GST_FLOW_OK); + reports = gst_validate_runner_get_reports (runner); + assert_equals_int (g_list_length (reports), 1); + g_list_free_full (reports, (GDestroyNotify) gst_validate_report_unref); + } + + /* clean up */ + fail_unless (gst_pad_activate_mode (srcpad, GST_PAD_MODE_PUSH, FALSE)); + fail_unless_equals_int (gst_element_set_state (sink, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + + _check_reports_refcount (srcpad, 2); + gst_object_unref (srcpad); + gst_check_object_destroyed_on_unref (sink); + ASSERT_OBJECT_REFCOUNT (runner, "runner", 2); + gst_object_unref (runner); +} + +GST_END_TEST; + +GST_START_TEST (buffer_outside_segment) +{ + GstPad *srcpad, *pad; + GstBuffer *buffer; + GstSegment segment; + GstElement *sink, *identity; + gchar *identity_klass; + GstValidateReport *report; + GstValidateRunner *runner; + GstValidateMonitor *monitor; + GList *reports; + + srcpad = gst_pad_new ("src", GST_PAD_SRC); + identity = gst_element_factory_make ("identity", NULL); + sink = gst_element_factory_make ("fakesink", "fakesink"); + + identity_klass = + g_strdup (gst_element_class_get_metadata (GST_ELEMENT_GET_CLASS + (identity), "klass")); + + /* Testing if a buffer is outside a segment is only done for buffer outputed + * from decoders for the moment, fake a Decoder so that the test is properly + * executed */ + gst_element_class_add_metadata (GST_ELEMENT_GET_CLASS (identity), "klass", + "Decoder"); + + pad = gst_element_get_static_pad (identity, "sink"); + fail_unless (gst_pad_link (srcpad, pad) == GST_PAD_LINK_OK); + gst_clear_object (&pad); + + fail_unless (gst_element_link (identity, sink)); + + fail_unless (g_setenv ("GST_VALIDATE_REPORTING_DETAILS", "all", TRUE)); + runner = gst_validate_runner_new (); + monitor = + gst_validate_monitor_factory_create (GST_OBJECT (identity), runner, NULL); + gst_validate_reporter_set_handle_g_logs (GST_VALIDATE_REPORTER (monitor)); + + pad = gst_element_get_static_pad (identity, "src"); + fail_unless (GST_IS_VALIDATE_PAD_MONITOR (g_object_get_data ((GObject *) + pad, "validate-monitor"))); + gst_clear_object (&pad); + + fail_unless (gst_pad_activate_mode (srcpad, GST_PAD_MODE_PUSH, TRUE)); + fail_unless_equals_int (gst_element_set_state (identity, GST_STATE_PLAYING), + GST_STATE_CHANGE_SUCCESS); + fail_unless_equals_int (gst_element_set_state (sink, GST_STATE_PLAYING), + GST_STATE_CHANGE_ASYNC); + + gst_segment_init (&segment, GST_FORMAT_TIME); + segment.start = 0; + segment.stop = GST_SECOND; + fail_unless (gst_pad_push_event (srcpad, + gst_event_new_stream_start ("the-stream"))); + fail_unless (gst_pad_push_event (srcpad, gst_event_new_segment (&segment))); + + /* Pushing a buffer that is outside the segment */ + { + buffer = gst_discont_buffer_new (); + GST_BUFFER_PTS (buffer) = 10 * GST_SECOND; + GST_BUFFER_DURATION (buffer) = GST_SECOND; + fail_if (GST_PAD_IS_FLUSHING (gst_element_get_static_pad (identity, + "sink"))); + fail_if (GST_PAD_IS_FLUSHING (gst_element_get_static_pad (identity, + "src"))); + fail_if (GST_PAD_IS_FLUSHING (gst_element_get_static_pad (sink, "sink"))); + fail_unless_equals_int (gst_pad_push (srcpad, buffer), GST_FLOW_OK); + + reports = gst_validate_runner_get_reports (runner); + assert_equals_int (g_list_length (reports), 1); + report = reports->data; + fail_unless_equals_int (report->level, GST_VALIDATE_REPORT_LEVEL_ISSUE); + fail_unless_equals_int (report->issue->issue_id, BUFFER_IS_OUT_OF_SEGMENT); + g_list_free_full (reports, (GDestroyNotify) gst_validate_report_unref); + } + + /* Pushing a buffer inside the segment */ + { + fail_unless_equals_int (gst_pad_push (srcpad, gst_discont_buffer_new ()), + GST_FLOW_OK); + reports = gst_validate_runner_get_reports (runner); + assert_equals_int (g_list_length (reports), 1); + g_list_free_full (reports, (GDestroyNotify) gst_validate_report_unref); + } + + + /* clean up */ + fail_unless (gst_pad_activate_mode (srcpad, GST_PAD_MODE_PUSH, FALSE)); + gst_object_unref (srcpad); + + gst_element_class_add_metadata (GST_ELEMENT_GET_CLASS (identity), "klass", + identity_klass); + g_free (identity_klass); + gst_object_unref (runner); + + fail_unless_equals_int (gst_element_set_state (identity, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + gst_element_unlink (identity, sink); + fail_unless_equals_int (gst_element_set_state (sink, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + gst_object_unref (identity); + gst_object_unref (sink); + gst_object_unref (monitor); +} + +GST_END_TEST; + +static void +fake_demuxer_prepare_pads (GstBin * pipeline, GstElement * demux, + GstValidateRunner * runner) +{ + gint i = 0; + GList *tmp; + + fail_unless (g_list_length (demux->srcpads), 3); + + for (tmp = demux->srcpads; tmp; tmp = tmp->next) { + GstPad *new_peer; + gchar *name = g_strdup_printf ("sink-%d", i++); + GstElement *sink = gst_element_factory_make ("fakesink", name); + + gst_bin_add (pipeline, sink); + + new_peer = sink->sinkpads->data; + gst_pad_link (tmp->data, new_peer); + gst_element_set_state (sink, GST_STATE_PLAYING); + gst_pad_activate_mode (tmp->data, GST_PAD_MODE_PUSH, TRUE); + + g_free (name); + } + + fail_unless (gst_pad_activate_mode (demux->sinkpads->data, GST_PAD_MODE_PUSH, + TRUE)); +} + +static void +_test_flow_aggregation (GstFlowReturn flow, GstFlowReturn flow1, + GstFlowReturn flow2, GstFlowReturn demux_flow, gboolean should_fail) +{ + GstPad *srcpad; + GstValidateReport *report; + GstPad *p, *p1, *p2; + GstElement *demuxer = fake_demuxer_new (); + GstBin *pipeline = GST_BIN (gst_pipeline_new ("validate-pipeline")); + GList *reports; + GstValidateRunner *runner; + + fail_unless (g_setenv ("GST_VALIDATE_REPORTING_DETAILS", "all", TRUE)); + runner = _start_monitoring_bin (pipeline); + + gst_bin_add (pipeline, demuxer); + fake_demuxer_prepare_pads (pipeline, demuxer, runner); + + srcpad = gst_pad_new ("srcpad1", GST_PAD_SRC); + gst_pad_link (srcpad, demuxer->sinkpads->data); + fail_unless (gst_pad_activate_mode (srcpad, GST_PAD_MODE_PUSH, TRUE)); + gst_check_setup_events_with_stream_id (srcpad, demuxer, NULL, + GST_FORMAT_TIME, "the-stream"); + + p = gst_pad_get_peer (demuxer->srcpads->data); + p1 = gst_pad_get_peer (demuxer->srcpads->next->data); + p2 = gst_pad_get_peer (demuxer->srcpads->next->next->data); + + p->ABI.abi.last_flowret = flow; + p1->ABI.abi.last_flowret = flow1; + p2->ABI.abi.last_flowret = flow2; + FAKE_DEMUXER (demuxer)->return_value = demux_flow; + + gst_object_unref (p); + gst_object_unref (p1); + gst_object_unref (p2); + + fail_unless_equals_int (gst_pad_push (srcpad, gst_discont_buffer_new ()), + demux_flow); + + reports = gst_validate_runner_get_reports (runner); + if (should_fail) { + assert_equals_int (g_list_length (reports), 1); + report = reports->data; + fail_unless_equals_int (report->level, GST_VALIDATE_REPORT_LEVEL_CRITICAL); + fail_unless_equals_int (report->issue->issue_id, WRONG_FLOW_RETURN); + } else { + assert_equals_int (g_list_length (reports), 0); + + } + + g_list_free_full (reports, (GDestroyNotify) gst_validate_report_unref); + clean_bus (GST_ELEMENT (pipeline)); + + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL); + ASSERT_OBJECT_REFCOUNT (pipeline, "ours", 1); + gst_object_ref (demuxer); + _stop_monitoring_bin (pipeline, runner); + + ASSERT_OBJECT_REFCOUNT (demuxer, "plop", 1); + gst_object_unref (demuxer); + gst_object_unref (srcpad); +} + +#define FLOW_TEST(name, flow1, flow2, flow3, demux_flow, fails) \ +GST_START_TEST(flow_aggregation_##name) { \ + _test_flow_aggregation (GST_FLOW_##flow1, GST_FLOW_##flow2, \ + GST_FLOW_##flow3, GST_FLOW_##demux_flow, fails); \ +} GST_END_TEST + +FLOW_TEST (ok_ok_error_ok, OK, OK, ERROR, OK, TRUE); +FLOW_TEST (eos_eos_eos_ok, EOS, EOS, EOS, OK, TRUE); +FLOW_TEST (not_neg_ok_ok_ok, NOT_NEGOTIATED, OK, OK, OK, TRUE); +/*[> Passing cases: <]*/ +FLOW_TEST (flushing_ok_ok_ok, FLUSHING, OK, OK, OK, FALSE); +FLOW_TEST (eos_eos_eos_eos, EOS, EOS, EOS, EOS, FALSE); +FLOW_TEST (eos_eos_ok_ok, EOS, EOS, OK, OK, FALSE); +FLOW_TEST (ok_ok_ok_eos, OK, OK, OK, EOS, FALSE); +FLOW_TEST (not_neg_ok_ok_not_neg, NOT_NEGOTIATED, OK, OK, NOT_NEGOTIATED, + FALSE); +#undef FLOW_TEST + +GST_START_TEST (issue_concatenation) +{ + GstPad *srcpad1, *srcpad2, *sinkpad, *fakemixer_sink1, *fakemixer_sink2; + GstElement *src1, *src2, *sink, *fakemixer; + GstValidateRunner *runner; + GstValidatePadMonitor *srcpad_monitor1, *srcpad_monitor2, *sinkpad_monitor; + GstValidatePadMonitor *fakemixer_sink_monitor1, *fakemixer_sink_monitor2; + GList *reports; + gint n_reports; + + fail_unless (g_setenv ("GST_VALIDATE_REPORTING_DETAILS", "subchain", TRUE)); + runner = gst_validate_runner_new (); + + src1 = create_and_monitor_element ("fakesrc2", NULL, runner); + src2 = create_and_monitor_element ("fakesrc2", NULL, runner); + fakemixer = create_and_monitor_element ("fakemixer", "fakemixer", runner); + sink = create_and_monitor_element ("fakesink", "fakesink", runner); + + srcpad1 = gst_element_get_static_pad (src1, "src"); + srcpad_monitor1 = g_object_get_data (G_OBJECT (srcpad1), "validate-monitor"); + srcpad2 = gst_element_get_static_pad (src2, "src"); + srcpad_monitor2 = g_object_get_data (G_OBJECT (srcpad2), "validate-monitor"); + fakemixer_sink1 = gst_element_request_pad_simple (fakemixer, "sink_%u"); + fakemixer_sink_monitor1 = + g_object_get_data (G_OBJECT (fakemixer_sink1), "validate-monitor"); + fakemixer_sink2 = gst_element_request_pad_simple (fakemixer, "sink_%u"); + fakemixer_sink_monitor2 = + g_object_get_data (G_OBJECT (fakemixer_sink2), "validate-monitor"); + sinkpad = gst_element_get_static_pad (sink, "sink"); + sinkpad_monitor = g_object_get_data (G_OBJECT (sinkpad), "validate-monitor"); + + fail_unless (gst_element_link (fakemixer, sink)); + fail_unless (gst_pad_link (srcpad1, fakemixer_sink1) == GST_PAD_LINK_OK); + fail_unless (gst_pad_link (srcpad2, fakemixer_sink2) == GST_PAD_LINK_OK); + + /* We want to handle the src behaviour ourselves */ + fail_unless (gst_pad_activate_mode (srcpad1, GST_PAD_MODE_PUSH, TRUE)); + fail_unless (gst_pad_activate_mode (srcpad2, GST_PAD_MODE_PUSH, TRUE)); + + gst_check_setup_events_with_stream_id (srcpad1, fakemixer, NULL, + GST_FORMAT_TIME, "stream1"); + gst_check_setup_events_with_stream_id (srcpad2, fakemixer, NULL, + GST_FORMAT_TIME, "stream2"); + + fail_unless_equals_int (gst_element_set_state (fakemixer, GST_STATE_PLAYING), + GST_STATE_CHANGE_SUCCESS); + fail_unless_equals_int (gst_element_set_state (sink, GST_STATE_PLAYING), + GST_STATE_CHANGE_ASYNC); + + + /* Send an unexpected flush stop */ + _gst_check_expecting_log = TRUE; + fail_unless (gst_pad_push_event (srcpad1, gst_event_new_flush_stop (TRUE))); + + /* The runner only sees one report */ + reports = gst_validate_runner_get_reports (runner); + assert_equals_int (g_list_length (reports), 1); + g_list_free_full (reports, (GDestroyNotify) gst_validate_report_unref); + + /* Each pad monitor on the way actually holds a report */ + n_reports = gst_validate_reporter_get_reports_count ((GstValidateReporter *) + srcpad_monitor1); + fail_unless_equals_int (n_reports, 1); + n_reports = gst_validate_reporter_get_reports_count ((GstValidateReporter *) + sinkpad_monitor); + fail_unless_equals_int (n_reports, 1); + n_reports = gst_validate_reporter_get_reports_count ((GstValidateReporter *) + fakemixer_sink_monitor1); + fail_unless_equals_int (n_reports, 1); + + /* But not the pad monitor of the other fakemixer sink */ + n_reports = gst_validate_reporter_get_reports_count ((GstValidateReporter *) + fakemixer_sink_monitor2); + fail_unless_equals_int (n_reports, 0); + n_reports = gst_validate_reporter_get_reports_count ((GstValidateReporter *) + srcpad_monitor2); + fail_unless_equals_int (n_reports, 0); + + /* Once again but on the other fakemixer sink */ + fail_unless (gst_pad_push_event (srcpad2, gst_event_new_flush_stop (TRUE))); + + /* The runner now sees two reports */ + reports = gst_validate_runner_get_reports (runner); + assert_equals_int (g_list_length (reports), 2); + g_list_free_full (reports, (GDestroyNotify) gst_validate_report_unref); + + /* These monitors already saw that issue */ + n_reports = gst_validate_reporter_get_reports_count ((GstValidateReporter *) + srcpad_monitor1); + fail_unless_equals_int (n_reports, 1); + n_reports = gst_validate_reporter_get_reports_count ((GstValidateReporter *) + sinkpad_monitor); + fail_unless_equals_int (n_reports, 1); + n_reports = gst_validate_reporter_get_reports_count ((GstValidateReporter *) + fakemixer_sink_monitor1); + fail_unless_equals_int (n_reports, 1); + + n_reports = gst_validate_reporter_get_reports_count ((GstValidateReporter *) + fakemixer_sink_monitor2); + fail_unless_equals_int (n_reports, 1); + n_reports = gst_validate_reporter_get_reports_count ((GstValidateReporter *) + srcpad_monitor2); + fail_unless_equals_int (n_reports, 1); + + /* clean up */ + fail_unless (gst_pad_activate_mode (srcpad1, GST_PAD_MODE_PUSH, FALSE)); + fail_unless (gst_pad_activate_mode (srcpad2, GST_PAD_MODE_PUSH, FALSE)); + fail_unless_equals_int (gst_element_set_state (fakemixer, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + fail_unless_equals_int (gst_element_set_state (sink, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + + /* The reporter, the runner */ + _check_reports_refcount (srcpad1, 2); + /* The reporter, the master report */ + _check_reports_refcount (fakemixer_sink1, 2); + free_element_monitor (src1); + free_element_monitor (src2); + free_element_monitor (fakemixer); + free_element_monitor (sink); + gst_object_unref (srcpad1); + gst_object_unref (srcpad2); + gst_object_unref (sinkpad); + gst_object_unref (fakemixer_sink1); + gst_object_unref (fakemixer_sink2); + gst_check_objects_destroyed_on_unref (fakemixer, fakemixer_sink1, + fakemixer_sink2, NULL); + gst_check_objects_destroyed_on_unref (src1, srcpad1, NULL); + gst_check_objects_destroyed_on_unref (src2, srcpad2, NULL); + gst_check_objects_destroyed_on_unref (sink, sinkpad, NULL); + ASSERT_OBJECT_REFCOUNT (runner, "runner", 2); + gst_object_unref (runner); +} + +GST_END_TEST; + +/* *INDENT-OFF* */ +static const gchar * media_info = +"" +" " +" " +" " /* buffer1 */ +" " /* buffer2 */ +" " /* buffer3 */ +" " /* gonna fail */ +" " /* buffer4 */ +" " /* buffer5 */ +" " /* buffer6 */ +" " /* gonna fail */ +" " +" " +" " +" " +""; +/* *INDENT-ON* */ + +typedef struct _BufferDesc +{ + const gchar *content; + GstClockTime pts; + GstClockTime dts; + GstClockTime duration; + gboolean keyframe; + + gint num_issues; +} BufferDesc; + +static GstBuffer * +_create_buffer (BufferDesc * bdesc) +{ + gchar *tmp = g_strdup (bdesc->content); + GstBuffer *buffer = + gst_buffer_new_wrapped (tmp, strlen (tmp) * sizeof (gchar)); + + GST_BUFFER_DTS (buffer) = bdesc->dts; + GST_BUFFER_PTS (buffer) = bdesc->pts; + GST_BUFFER_DURATION (buffer) = bdesc->duration; + + if (bdesc->keyframe) + GST_BUFFER_FLAG_UNSET (buffer, GST_BUFFER_FLAG_DELTA_UNIT); + else + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT); + + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); + + return buffer; +} + +static void +_check_media_info (GstSegment * segment, BufferDesc * bufs) +{ + GstEvent *segev; + GstBuffer *buffer; + GstElement *decoder; + GstPad *srcpad, *sinkpad; + GstValidateReport *report; + GstValidateMonitor *monitor; + GstValidateRunner *runner; + GstValidateMediaDescriptor *mdesc; + GstCaps *caps; + + GError *err = NULL; + gint i, num_issues = 0; + + fail_unless (g_setenv ("GST_VALIDATE_REPORTING_DETAILS", "all", TRUE)); + runner = gst_validate_runner_new (); + + mdesc = (GstValidateMediaDescriptor *) + gst_validate_media_descriptor_parser_new_from_xml (runner, media_info, + &err); + + decoder = fake_decoder_new (); + monitor = _start_monitoring_element (decoder, runner); + gst_validate_monitor_set_media_descriptor (monitor, mdesc); + gst_object_unref (mdesc); + + srcpad = gst_pad_new ("src", GST_PAD_SRC); + sinkpad = decoder->sinkpads->data; + ASSERT_OBJECT_REFCOUNT (sinkpad, "decoder ref", 1); + fail_unless (gst_pad_activate_mode (srcpad, GST_PAD_MODE_PUSH, TRUE)); + fail_unless_equals_int (gst_element_set_state (decoder, GST_STATE_PLAYING), + GST_STATE_CHANGE_SUCCESS); + + assert_equals_string (gst_pad_link_get_name (gst_pad_link (srcpad, sinkpad)), + gst_pad_link_get_name (GST_PAD_LINK_OK)); + + caps = + gst_caps_from_string + ("video/x-raw, width=360, height=42, framerate=24/1, pixel-aspect-ratio =1/1, format=AYUV"); + gst_check_setup_events_with_stream_id (srcpad, decoder, caps, GST_FORMAT_TIME, + "the-stream"); + gst_caps_unref (caps); + + if (segment) { + segev = gst_event_new_segment (segment); + fail_unless (gst_pad_push_event (srcpad, segev)); + } + + for (i = 0; bufs[i].content != NULL; i++) { + GList *reports; + BufferDesc *buf = &bufs[i]; + buffer = _create_buffer (buf); + + assert_equals_string (gst_flow_get_name (gst_pad_push (srcpad, buffer)), + gst_flow_get_name (GST_FLOW_OK)); + reports = gst_validate_runner_get_reports (runner); + + num_issues += buf->num_issues; + assert_equals_int (g_list_length (reports), num_issues); + + if (buf->num_issues) { + GList *tmp = g_list_nth (reports, num_issues - buf->num_issues); + + while (tmp) { + report = tmp->data; + + fail_unless_equals_int (report->level, + GST_VALIDATE_REPORT_LEVEL_WARNING); + fail_unless_equals_int (report->issue->issue_id, WRONG_BUFFER); + tmp = tmp->next; + } + } + g_list_free_full (reports, (GDestroyNotify) gst_validate_report_unref); + } + + /* clean up */ + fail_unless (gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PUSH, FALSE)); + fail_unless_equals_int (gst_element_set_state (decoder, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + + gst_object_unref (srcpad); + gst_check_objects_destroyed_on_unref (decoder, sinkpad, NULL); + ASSERT_OBJECT_REFCOUNT (runner, "runner", 2); + gst_object_unref (runner); + gst_object_unref (monitor); +} + +#define MEDIA_INFO_TEST(name,segment_start,bufs) \ +GST_START_TEST(media_info_##name) { \ + if (segment_start >= 0) { \ + GstSegment segment; \ + gst_segment_init (&segment, GST_FORMAT_TIME); \ + segment.start = segment_start; \ + _check_media_info (&segment, (bufs)); \ + } else \ + _check_media_info (NULL, (bufs)); \ +} GST_END_TEST + +/* *INDENT-OFF* */ +MEDIA_INFO_TEST (1, -1, + ((BufferDesc []) { + { + .content = "buffer1", + .pts = 0, + .dts = 0, + .duration = 1, + .keyframe = TRUE, + .num_issues = 0 + }, + { + .content = "buffer2", + .pts = 1, + .dts = 1, + .duration = 1, + .keyframe = FALSE, + .num_issues = 0 + }, + { + .content = "buffer3", + .pts = 2, + .dts = 2, + .duration = 1, + .keyframe = FALSE, + .num_issues = 0 + }, + { + .content = "fail please", + .pts = 3, + .dts = 3, + .duration = 1, + .keyframe = FALSE, + .num_issues = 1 + }, + { NULL} + })); + +/* Segment start is 2, the first buffer is expected (first Keyframe) */ +MEDIA_INFO_TEST (2, 2, + ((BufferDesc []) { + { + .content = "buffer2", /* Wrong checksum */ + .pts = 0, + .dts = 0, + .duration = 1, + .keyframe = TRUE, + .num_issues = 1 + }, + { NULL} + })); + +/* Segment start is 2, the first buffer is expected (first Keyframe) */ +MEDIA_INFO_TEST (3, 2, + ((BufferDesc []) { + { /* The right first buffer */ + .content = "buffer1", + .pts = 0, + .dts = 0, + .duration = 1, + .keyframe = TRUE, + .num_issues = 0 + }, + { NULL} + })); + +/* Segment start is 6, the 4th buffer is expected (first Keyframe) */ +MEDIA_INFO_TEST (4, 6, + ((BufferDesc []) { + { /* The right fourth buffer */ + .content = "buffer4", + .pts = 4, + .dts = 4, + .duration = 1, + .keyframe = TRUE, + .num_issues = 0 + }, + { NULL} + })); + +/* Segment start is 6, the 4th buffer is expected (first Keyframe) */ +MEDIA_INFO_TEST (5, 6, + ((BufferDesc []) { + { /* The sixth buffer... all wrong! */ + .content = "buffer6", + .pts = 6, + .dts = 6, + .duration = 1, + .keyframe = FALSE, + .num_issues = 1 + }, + { NULL} + })); +/* *INDENT-ON* */ + +GST_START_TEST (caps_events) +{ + GstPad *srcpad, *sinkpad; + GstElement *decoder = fake_decoder_new (); + GstElement *sink = gst_element_factory_make ("fakesink", NULL); + GstBin *pipeline = GST_BIN (gst_pipeline_new ("validate-pipeline")); + GList *reports; + GstValidateReport *report; + GstValidateRunner *runner; + GstCaps *caps; + + fail_unless (g_setenv ("GST_VALIDATE_REPORTING_DETAILS", "all", TRUE)); + runner = _start_monitoring_bin (pipeline); + + gst_bin_add_many (pipeline, decoder, sink, NULL); + srcpad = gst_pad_new ("srcpad1", GST_PAD_SRC); + sinkpad = decoder->sinkpads->data; + gst_pad_link (srcpad, sinkpad); + + gst_element_link (decoder, sink); + fail_unless_equals_int (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING), GST_STATE_CHANGE_ASYNC); + fail_unless (gst_pad_activate_mode (srcpad, GST_PAD_MODE_PUSH, TRUE)); + + reports = gst_validate_runner_get_reports (runner); + assert_equals_int (g_list_length (reports), 0); + g_list_free_full (reports, (GDestroyNotify) gst_validate_report_unref); + + caps = + gst_caps_from_string + ("video/x-raw, format=AYUV, width=320, height=240, pixel-aspect-ratio=1/1"); + fail_unless (gst_pad_push_event (srcpad, gst_event_new_caps (caps))); + gst_caps_unref (caps); + reports = gst_validate_runner_get_reports (runner); + + /* Our caps didn't have a framerate, the decoder sink should complain about + * that */ + assert_equals_int (g_list_length (reports), 1); + report = reports->data; + fail_unless_equals_int (report->level, GST_VALIDATE_REPORT_LEVEL_ISSUE); + fail_unless_equals_int (report->issue->issue_id, CAPS_IS_MISSING_FIELD); + g_list_free_full (reports, (GDestroyNotify) gst_validate_report_unref); + + caps = + gst_caps_from_string + ("video/x-raw, format=AYUV, framerate=24/1, width=(fraction)320, height=240, pixel-aspect-ratio=1/1"); + fail_unless (gst_pad_push_event (srcpad, gst_event_new_caps (caps))); + gst_caps_unref (caps); + + reports = gst_validate_runner_get_reports (runner); + assert_equals_int (g_list_length (reports), 2); + report = reports->next->data; + /* A width isn't supposed to be a fraction */ + fail_unless_equals_int (report->level, GST_VALIDATE_REPORT_LEVEL_WARNING); + fail_unless_equals_int (report->issue->issue_id, CAPS_FIELD_HAS_BAD_TYPE); + g_list_free_full (reports, (GDestroyNotify) gst_validate_report_unref); + + caps = + gst_caps_from_string + ("video/x-raw, format=AYUV, framerate=24/1, width=320, height=240, pixel-aspect-ratio=1/1"); + fail_unless (gst_pad_push_event (srcpad, gst_event_new_caps (caps))); + gst_caps_unref (caps); + + caps = + gst_caps_from_string + ("video/x-raw, format=AYUV, framerate=24/1, width=320, height=240, pixel-aspect-ratio=1/1"); + fail_unless (gst_pad_push_event (srcpad, gst_event_new_caps (caps))); + gst_caps_unref (caps); + + reports = gst_validate_runner_get_reports (runner); + assert_equals_int (g_list_length (reports), 3); + report = reports->next->next->data; + /* A width isn't supposed to be a fraction */ + fail_unless_equals_int (report->level, GST_VALIDATE_REPORT_LEVEL_WARNING); + /* Pushing the same twice isn't very useful */ + fail_unless_equals_int (report->issue->issue_id, EVENT_CAPS_DUPLICATE); + + + clean_bus (GST_ELEMENT (pipeline)); + + g_list_free_full (reports, (GDestroyNotify) gst_validate_report_unref); + + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL); + _stop_monitoring_bin (pipeline, runner); + + gst_object_unref (srcpad); +} + +GST_END_TEST; + +GST_START_TEST (eos_without_segment) +{ + GstPad *srcpad, *sinkpad; + GstValidateReport *report; + GstElement *decoder = fake_decoder_new (); + GstElement *sink = gst_element_factory_make ("fakesink", NULL); + GstBin *pipeline = GST_BIN (gst_pipeline_new ("validate-pipeline")); + GList *reports; + GstValidateRunner *runner; + + fail_unless (g_setenv ("GST_VALIDATE_REPORTING_DETAILS", "all", TRUE)); + runner = _start_monitoring_bin (pipeline); + + gst_bin_add_many (pipeline, decoder, sink, NULL); + srcpad = gst_pad_new ("srcpad1", GST_PAD_SRC); + sinkpad = decoder->sinkpads->data; + gst_pad_link (srcpad, sinkpad); + + gst_element_link (decoder, sink); + fail_unless_equals_int (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING), GST_STATE_CHANGE_ASYNC); + fail_unless (gst_pad_activate_mode (srcpad, GST_PAD_MODE_PUSH, TRUE)); + + reports = gst_validate_runner_get_reports (runner); + assert_equals_int (g_list_length (reports), 0); + g_list_free_full (reports, (GDestroyNotify) gst_validate_report_unref); + + fail_unless (gst_pad_push_event (srcpad, gst_event_new_eos ())); + reports = gst_validate_runner_get_reports (runner); + + /* Getting the issue on the srcpad -> decoder.sinkpad -> decoder->srcpad */ + assert_equals_int (g_list_length (reports), 3); + report = reports->data; + fail_unless_equals_int (report->level, GST_VALIDATE_REPORT_LEVEL_WARNING); + fail_unless_equals_int (report->issue->issue_id, EVENT_EOS_WITHOUT_SEGMENT); + clean_bus (GST_ELEMENT (pipeline)); + + g_list_free_full (reports, (GDestroyNotify) gst_validate_report_unref); + + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL); + _stop_monitoring_bin (pipeline, runner); + + gst_object_unref (srcpad); +} + +GST_END_TEST; + +GST_START_TEST (buffer_timestamp_out_of_received_range) +{ + GstPad *srcpad, *sinkpad; + GstElement *decoder = fake_decoder_new (); + GstElement *sink = gst_element_factory_make ("fakesink", NULL); + GstBin *pipeline = GST_BIN (gst_pipeline_new ("validate-pipeline")); + GList *reports; + GstValidateRunner *runner; + GstSegment segment; + GstBuffer *buffer; + GstPad *decoder_srcpad; + GstValidateReport *report; + + fail_unless (g_setenv ("GST_VALIDATE_REPORTING_DETAILS", "all", TRUE)); + runner = _start_monitoring_bin (pipeline); + + gst_bin_add_many (pipeline, decoder, sink, NULL); + srcpad = gst_pad_new ("srcpad1", GST_PAD_SRC); + sinkpad = decoder->sinkpads->data; + gst_pad_link (srcpad, sinkpad); + + gst_element_link (decoder, sink); + ASSERT_SET_STATE (GST_ELEMENT (pipeline), GST_STATE_PLAYING, + GST_STATE_CHANGE_ASYNC); + fail_unless (gst_pad_activate_mode (srcpad, GST_PAD_MODE_PUSH, TRUE)); + + gst_segment_init (&segment, GST_FORMAT_TIME); + segment.start = 0; + segment.stop = GST_SECOND; + fail_unless (gst_pad_push_event (srcpad, + gst_event_new_stream_start ("the-stream"))); + fail_unless (gst_pad_push_event (srcpad, gst_event_new_segment (&segment))); + + { + buffer = gst_discont_buffer_new (); + GST_BUFFER_PTS (buffer) = 0 * GST_SECOND; + GST_BUFFER_DURATION (buffer) = 0.1 * GST_SECOND; + fail_unless (gst_pad_push (srcpad, buffer) == GST_FLOW_OK); + } + + decoder_srcpad = gst_element_get_static_pad (decoder, "src"); + + { + buffer = gst_discont_buffer_new (); + GST_BUFFER_PTS (buffer) = 0.9 * GST_SECOND; + GST_BUFFER_DURATION (buffer) = 0.1 * GST_SECOND; + fail_unless (gst_pad_push (decoder_srcpad, buffer) == GST_FLOW_OK); + } + + reports = gst_validate_runner_get_reports (runner); + + assert_equals_int (g_list_length (reports), 1); + report = reports->data; + fail_unless_equals_int (report->level, GST_VALIDATE_REPORT_LEVEL_WARNING); + fail_unless_equals_int (report->issue->issue_id, + BUFFER_TIMESTAMP_OUT_OF_RECEIVED_RANGE); + g_list_free_full (reports, (GDestroyNotify) gst_validate_report_unref); + + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL); + + gst_object_unref (decoder_srcpad); + gst_object_unref (srcpad); + + _stop_monitoring_bin (pipeline, runner); +} + +GST_END_TEST; + + +GST_START_TEST (flow_error_without_message) +{ + GstElement *decoder = fake_decoder_new (); + GstElement *src = gst_element_factory_make ("fakesrc", NULL); + GstElement *sink = gst_element_factory_make ("fakesink", NULL); + GstBin *pipeline = GST_BIN (gst_pipeline_new ("validate-pipeline")); + GList *reports; + GstValidateRunner *runner; + GstValidateReport *report; + + fail_unless (g_setenv ("GST_VALIDATE_REPORTING_DETAILS", "all", TRUE)); + runner = _start_monitoring_bin (pipeline); + + gst_bin_add_many (pipeline, src, decoder, sink, NULL); + fail_unless (gst_element_link_many (src, decoder, sink, NULL)); + + FAKE_DECODER (decoder)->return_value = GST_FLOW_ERROR; + ASSERT_SET_STATE (GST_ELEMENT (pipeline), GST_STATE_PLAYING, + GST_STATE_CHANGE_ASYNC); + + gst_element_get_state (GST_ELEMENT (pipeline), NULL, NULL, + GST_CLOCK_TIME_NONE); + + reports = gst_validate_runner_get_reports (runner); + + fail_unless (g_list_length (reports) >= 1); + report = reports->data; + fail_unless_equals_int (report->level, GST_VALIDATE_REPORT_LEVEL_WARNING); + fail_unless_equals_int (report->issue->issue_id, + FLOW_ERROR_WITHOUT_ERROR_MESSAGE); + g_list_free_full (reports, (GDestroyNotify) gst_validate_report_unref); + + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL); + + _stop_monitoring_bin (pipeline, runner); +} + +GST_END_TEST; + + +GST_START_TEST (flow_error_with_message) +{ + GstElement *decoder = fake_decoder_new (); + GstElement *src = gst_element_factory_make ("fakesrc", NULL); + GstElement *sink = gst_element_factory_make ("fakesink", NULL); + GstBin *pipeline = GST_BIN (gst_pipeline_new ("validate-pipeline")); + GList *reports; + GstValidateRunner *runner; + GstValidateReport *report; + const GError gerror = + { G_IO_ERROR, G_IO_ERROR_FAILED, (gchar *) "fake error" }; + + fail_unless (g_setenv ("GST_VALIDATE_REPORTING_DETAILS", "all", TRUE)); + runner = _start_monitoring_bin (pipeline); + + gst_bin_add_many (pipeline, src, decoder, sink, NULL); + fail_unless (gst_element_link_many (src, decoder, sink, NULL)); + + g_object_set (src, "is-live", TRUE, NULL); + + FAKE_DECODER (decoder)->return_value = GST_FLOW_ERROR; + + ASSERT_SET_STATE (GST_ELEMENT (pipeline), GST_STATE_PAUSED, + GST_STATE_CHANGE_NO_PREROLL); + + gst_element_post_message (decoder, + gst_message_new_error (GST_OBJECT (decoder), + (GError *) & gerror, "This is a fake error")); + + ASSERT_SET_STATE (GST_ELEMENT (pipeline), GST_STATE_PLAYING, + GST_STATE_CHANGE_ASYNC); + + gst_element_get_state (GST_ELEMENT (pipeline), NULL, NULL, + GST_CLOCK_TIME_NONE); + + reports = gst_validate_runner_get_reports (runner); + + assert_equals_int (g_list_length (reports), 1); + report = reports->data; + fail_unless_equals_int (report->issue->issue_id, ERROR_ON_BUS); + g_list_free_full (reports, (GDestroyNotify) gst_validate_report_unref); + + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL); + + _stop_monitoring_bin (pipeline, runner); +} + +GST_END_TEST; + + + +static Suite * +gst_validate_suite (void) +{ + Suite *s = suite_create ("padmonitor"); + TCase *tc_chain = tcase_create ("padmonitor"); + suite_add_tcase (s, tc_chain); + + if (atexit (gst_validate_deinit) != 0) { + GST_ERROR ("failed to set gst_validate_deinit as exit function"); + } + + fake_elements_register (); + + tcase_add_test (tc_chain, buffer_before_segment); + tcase_add_test (tc_chain, buffer_outside_segment); + tcase_add_test (tc_chain, buffer_timestamp_out_of_received_range); + + tcase_add_test (tc_chain, media_info_1); + tcase_add_test (tc_chain, media_info_2); + tcase_add_test (tc_chain, media_info_3); + tcase_add_test (tc_chain, media_info_4); + tcase_add_test (tc_chain, media_info_5); + + tcase_add_test (tc_chain, flow_aggregation_ok_ok_error_ok); + tcase_add_test (tc_chain, flow_aggregation_eos_eos_eos_ok); + tcase_add_test (tc_chain, flow_aggregation_flushing_ok_ok_ok); + tcase_add_test (tc_chain, flow_aggregation_not_neg_ok_ok_ok); + tcase_add_test (tc_chain, flow_aggregation_eos_eos_eos_eos); + tcase_add_test (tc_chain, flow_aggregation_eos_eos_ok_ok); + tcase_add_test (tc_chain, flow_aggregation_ok_ok_ok_eos); + tcase_add_test (tc_chain, flow_aggregation_not_neg_ok_ok_not_neg); + + tcase_add_test (tc_chain, issue_concatenation); + tcase_add_test (tc_chain, eos_without_segment); + tcase_add_test (tc_chain, caps_events); + tcase_add_test (tc_chain, flow_error_without_message); + tcase_add_test (tc_chain, flow_error_with_message); + + return s; +} + +GST_CHECK_MAIN (gst_validate); diff --git a/validate/tests/check/validate/reporting.c b/validate/tests/check/validate/reporting.c new file mode 100644 index 0000000000..e83f53b1a3 --- /dev/null +++ b/validate/tests/check/validate/reporting.c @@ -0,0 +1,286 @@ +/* GstValidate + * Copyright (C) 2014 Mathieu Duponchelle + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include "test-utils.h" + +GST_START_TEST (test_report_levels_all) +{ + GstValidateRunner *runner; + + /* FIXME: for now the only interface to set the reporting level is through an + * environment variable parsed at the time of the runner initialization, + * we can simplify that if the runner exposes API for that at some point + */ + + /* Try to set the default reporting level to ALL, the code is supposed to + * be case insensitive */ + fail_unless (g_setenv ("GST_VALIDATE_REPORTING_DETAILS", "AlL", TRUE)); + runner = gst_validate_runner_new (); + fail_unless (gst_validate_runner_get_default_reporting_level (runner) == + GST_VALIDATE_SHOW_ALL); + g_object_unref (runner); +} + +GST_END_TEST; + + +GST_START_TEST (test_report_levels_2) +{ + GstValidateRunner *runner; + + /* Try to set the default reporting level to subchain, the code is supposed to + * parse numbers as well */ + fail_unless (g_setenv ("GST_VALIDATE_REPORTING_DETAILS", "2", TRUE)); + runner = gst_validate_runner_new (); + fail_unless (gst_validate_runner_get_default_reporting_level (runner) == + GST_VALIDATE_SHOW_SYNTHETIC); + g_object_unref (runner); +} + +GST_END_TEST; + +GST_START_TEST (test_report_levels_complex_parsing) +{ + GstValidateRunner *runner; + + /* Try to set the reporting level for an object */ + fail_unless (g_setenv ("GST_VALIDATE_REPORTING_DETAILS", + "synthetic,test_object:monitor,other_*:all", TRUE)); + runner = gst_validate_runner_new (); + fail_unless (gst_validate_runner_get_reporting_level_for_name (runner, + "test_object") == GST_VALIDATE_SHOW_MONITOR); + fail_unless (gst_validate_runner_get_reporting_level_for_name (runner, + "other_test_object") == GST_VALIDATE_SHOW_ALL); + fail_unless (gst_validate_runner_get_reporting_level_for_name (runner, + "dummy_test_object") == GST_VALIDATE_SHOW_UNKNOWN); + + g_object_unref (runner); +} + +GST_END_TEST; + +GST_START_TEST (test_complex_reporting_details) +{ + GstPad *pad; + GstObject *pipeline; + GstElement *element; + GError *error = NULL; + GstValidateMonitor *monitor, *pipeline_monitor; + GstValidateRunner *runner; + + /* Now let's try to see if the created monitors actually understand the + * situation they've put themselves into */ + fail_unless (g_setenv ("GST_VALIDATE_REPORTING_DETAILS", + "none,pipeline*:monitor,sofake1:all,sofake*::sink:subchain", TRUE)); + runner = gst_validate_runner_new (); + + pipeline = (GstObject *) + gst_parse_launch ("fakesrc name=sofake1 ! fakesink name=sofake2", &error); + fail_unless (pipeline != NULL); + pipeline_monitor = + gst_validate_monitor_factory_create (GST_OBJECT (pipeline), runner, NULL); + + element = gst_bin_get_by_name (GST_BIN (pipeline), "sofake1"); + monitor = + (GstValidateMonitor *) g_object_get_data (G_OBJECT (element), + "validate-monitor"); + fail_unless (gst_validate_reporter_get_reporting_level (GST_VALIDATE_REPORTER + (monitor)) == GST_VALIDATE_SHOW_ALL); + + pad = gst_element_get_static_pad (element, "src"); + monitor = + (GstValidateMonitor *) g_object_get_data (G_OBJECT (pad), + "validate-monitor"); + /* The pad should have inherited the reporting level */ + fail_unless (gst_validate_reporter_get_reporting_level (GST_VALIDATE_REPORTER + (monitor)) == GST_VALIDATE_SHOW_ALL); + gst_object_unref (pad); + + gst_object_unref (element); + + element = gst_bin_get_by_name (GST_BIN (pipeline), "sofake2"); + monitor = + (GstValidateMonitor *) g_object_get_data (G_OBJECT (element), + "validate-monitor"); + /* The element should have inherited its reporting level from the pipeline */ + fail_unless (gst_validate_reporter_get_reporting_level (GST_VALIDATE_REPORTER + (monitor)) == GST_VALIDATE_SHOW_MONITOR); + + pad = gst_element_get_static_pad (element, "sink"); + monitor = + (GstValidateMonitor *) g_object_get_data (G_OBJECT (pad), + "validate-monitor"); + /* But its pad should not as it falls in the sofake*::sink pattern */ + fail_unless (gst_validate_reporter_get_reporting_level (GST_VALIDATE_REPORTER + (monitor)) == GST_VALIDATE_SHOW_SUBCHAIN); + gst_object_unref (pad); + + gst_object_unref (element); + + g_object_unref (pipeline_monitor); + gst_object_unref (pipeline); + g_object_unref (runner); +} + +GST_END_TEST; + +static void +_create_issues (GstValidateRunner * runner) +{ + GstPad *srcpad1, *srcpad2, *sinkpad, *funnel_sink1, *funnel_sink2; + GstElement *src1, *src2, *sink, *fakemixer; + GstSegment segment; + + src1 = create_and_monitor_element ("fakesrc2", "fakesrc1", runner); + src2 = create_and_monitor_element ("fakesrc2", "fakesrc2", runner); + fakemixer = create_and_monitor_element ("fakemixer", "fakemixer", runner); + sink = create_and_monitor_element ("fakesink", "fakesink", runner); + + srcpad1 = gst_element_get_static_pad (src1, "src"); + srcpad2 = gst_element_get_static_pad (src2, "src"); + funnel_sink1 = gst_element_request_pad_simple (fakemixer, "sink_%u"); + funnel_sink2 = gst_element_request_pad_simple (fakemixer, "sink_%u"); + sinkpad = gst_element_get_static_pad (sink, "sink"); + + fail_unless (gst_element_link (fakemixer, sink)); + fail_unless (gst_pad_link (srcpad1, funnel_sink1) == GST_PAD_LINK_OK); + fail_unless (gst_pad_link (srcpad2, funnel_sink2) == GST_PAD_LINK_OK); + + /* We want to handle the src behaviour ourselves */ + fail_unless (gst_pad_activate_mode (srcpad1, GST_PAD_MODE_PUSH, TRUE)); + fail_unless (gst_pad_activate_mode (srcpad2, GST_PAD_MODE_PUSH, TRUE)); + + /* Setup all needed events */ + gst_segment_init (&segment, GST_FORMAT_TIME); + segment.start = 0; + segment.stop = GST_SECOND; + + fail_unless (gst_pad_push_event (srcpad1, + gst_event_new_stream_start ("the-stream"))); + fail_unless (gst_pad_push_event (srcpad1, gst_event_new_segment (&segment))); + + fail_unless (gst_pad_push_event (srcpad2, + gst_event_new_stream_start ("the-stream"))); + fail_unless (gst_pad_push_event (srcpad2, gst_event_new_segment (&segment))); + + fail_unless_equals_int (gst_element_set_state (fakemixer, GST_STATE_PLAYING), + GST_STATE_CHANGE_SUCCESS); + fail_unless_equals_int (gst_element_set_state (sink, GST_STATE_PLAYING), + GST_STATE_CHANGE_ASYNC); + + /* Send an unexpected flush stop */ + _gst_check_expecting_log = TRUE; + fail_unless (gst_pad_push_event (srcpad1, gst_event_new_flush_stop (TRUE))); + + /* Once again but on the other fakemixer sink */ + fail_unless (gst_pad_push_event (srcpad2, gst_event_new_flush_stop (TRUE))); + + /* clean up */ + fail_unless (gst_pad_activate_mode (srcpad1, GST_PAD_MODE_PUSH, FALSE)); + fail_unless (gst_pad_activate_mode (srcpad2, GST_PAD_MODE_PUSH, FALSE)); + fail_unless_equals_int (gst_element_set_state (fakemixer, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + fail_unless_equals_int (gst_element_set_state (sink, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + + gst_object_unref (srcpad1); + gst_object_unref (srcpad2); + gst_object_unref (sinkpad); + gst_object_unref (funnel_sink1); + gst_object_unref (funnel_sink2); + gst_check_objects_destroyed_on_unref (fakemixer, funnel_sink1, funnel_sink2, + NULL); + gst_check_objects_destroyed_on_unref (src1, srcpad1, NULL); + gst_check_objects_destroyed_on_unref (src2, srcpad2, NULL); + gst_check_objects_destroyed_on_unref (sink, sinkpad, NULL); +} + +#define TEST_LEVELS(name, details, num_issues) \ +GST_START_TEST (test_global_level_##name) { \ + GstValidateRunner *runner; \ + fail_unless (g_setenv ("GST_VALIDATE_REPORTING_DETAILS", details, TRUE)); \ + runner = gst_validate_runner_new (); \ + _create_issues (runner); \ + fail_unless_equals_int (gst_validate_runner_get_reports_count (runner), num_issues); \ + g_object_unref (runner); \ +} GST_END_TEST + +TEST_LEVELS (none, "none", 0); +TEST_LEVELS (synthetic, "synthetic", 1); +TEST_LEVELS (monitor, "monitor", 6); +TEST_LEVELS (all, "all", 8); +TEST_LEVELS (none_fakesink_synthetic, "none,fakesrc1:synthetic", 1); +/* 5 issues because all pads will report their own issues separately, except +* for the sink which will not report an issue */ +TEST_LEVELS (monitor_sink_none, "monitor,sink:none", 5); +/* 3 issues because both fake sources will have subsequent subchains of +* issues, and the sink will report its issue separately */ +TEST_LEVELS (subchain_sink_monitor, "subchain,sink:monitor", 3); + +/* 4 issues because the fakemixer sink issues will be concatenated with the +* fakesrc issues, the fakemixer src will report its issue separately, and the +* sink will not find a report immediately upstream */ +TEST_LEVELS + (synthetic_fakesrc1_subchain_fakesrc2_subchain_fakemixer_src_monitor, + "synthetic,fakesrc1:subchain,fakesrc2:subchain,fakemixer*::src*:monitor", + 4); + +/* 2 issues repeated on the fakesink's sink */ +TEST_LEVELS (none_fakesink_all, "none,fakesink*:all", 2); + +TEST_LEVELS (issue_type, "event::flush-stop-unexpected:none", 0); + +#undef TEST_LEVELS + +static Suite * +gst_validate_suite (void) +{ + Suite *s = suite_create ("reporting"); + TCase *tc_chain = tcase_create ("reporting"); + suite_add_tcase (s, tc_chain); + + if (atexit (gst_validate_deinit) != 0) { + GST_ERROR ("failed to set gst_validate_deinit as exit function"); + } + + fake_elements_register (); + + tcase_add_test (tc_chain, test_report_levels_all); + tcase_add_test (tc_chain, test_report_levels_2); + tcase_add_test (tc_chain, test_report_levels_complex_parsing); + tcase_add_test (tc_chain, test_complex_reporting_details); + + tcase_add_test (tc_chain, test_global_level_none); + tcase_add_test (tc_chain, test_global_level_synthetic); + tcase_add_test (tc_chain, test_global_level_monitor); + tcase_add_test (tc_chain, test_global_level_all); + tcase_add_test (tc_chain, test_global_level_none_fakesink_synthetic); + tcase_add_test (tc_chain, test_global_level_monitor_sink_none); + tcase_add_test (tc_chain, test_global_level_subchain_sink_monitor); + tcase_add_test (tc_chain, + test_global_level_synthetic_fakesrc1_subchain_fakesrc2_subchain_fakemixer_src_monitor); + tcase_add_test (tc_chain, test_global_level_none_fakesink_all); + tcase_add_test (tc_chain, test_global_level_issue_type); + + return s; +} + +GST_CHECK_MAIN (gst_validate); diff --git a/validate/tests/check/validate/scenario.c b/validate/tests/check/validate/scenario.c new file mode 100644 index 0000000000..adaaf31689 --- /dev/null +++ b/validate/tests/check/validate/scenario.c @@ -0,0 +1,64 @@ +#include +#include +#include + +GST_START_TEST (test_expression_parser) +{ + GstClockTime start; + GstValidateRunner *runner = gst_validate_runner_new (); + GstValidateActionType *set_vars = gst_validate_get_action_type ("set-vars"); + GstValidateActionType *seek_type = gst_validate_get_action_type ("seek"); + GstValidateScenario *scenario = + g_object_new (GST_TYPE_VALIDATE_SCENARIO, "validate-runner", + runner, NULL); + GstValidateAction *action; + GstStructure *st; + + fail_unless (seek_type); + + st = gst_structure_from_string + ("set-vars, a=(string)\"50\", b=(string)\"70\", default_flags=flush", + NULL); + action = gst_validate_action_new (scenario, set_vars, st, FALSE); + fail_unless_equals_int (gst_validate_execute_action (set_vars, action), + GST_VALIDATE_EXECUTE_ACTION_OK); + gst_structure_free (st); + gst_validate_action_unref (action); + + st = gst_structure_from_string + ("seek, start=\"min($(a), $(b))\", flags=\"$(default_flags)\"", NULL); + action = gst_validate_action_new (scenario, seek_type, st, FALSE); + gst_structure_free (st); + fail_unless (action); + + fail_unless (seek_type->prepare (action)); + fail_unless (gst_validate_action_get_clocktime (scenario, action, "start", + &start)); + fail_unless_equals_uint64 (start, 50 * GST_SECOND); + gst_validate_action_unref (action); + + gst_object_unref (runner); +} + +GST_END_TEST; + +static Suite * +gst_validate_suite (void) +{ + Suite *s = suite_create ("registry"); + TCase *tc_chain = tcase_create ("registry"); + suite_add_tcase (s, tc_chain); + + if (atexit (gst_validate_deinit) != 0) { + GST_ERROR ("failed to set gst_validate_deinit as exit function"); + } + + g_setenv ("GST_VALIDATE_REPORTING_DETAILS", "all", TRUE); + gst_validate_init (); + tcase_add_test (tc_chain, test_expression_parser); + gst_validate_deinit (); + + return s; +} + +GST_CHECK_MAIN (gst_validate); diff --git a/validate/tests/check/validate/test-utils.c b/validate/tests/check/validate/test-utils.c new file mode 100644 index 0000000000..31d0e50816 --- /dev/null +++ b/validate/tests/check/validate/test-utils.c @@ -0,0 +1,521 @@ +/* GstValidate + * Copyright (C) 2014 Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "test-utils.h" + +typedef struct _DestroyedObjectStruct +{ + GObject *object; + gboolean destroyed; +} DestroyedObjectStruct; + +static void +weak_notify (DestroyedObjectStruct * destroyed, GObject ** object) +{ + destroyed->destroyed = TRUE; +} + +void +check_destroyed (gpointer object_to_unref, gpointer first_object, ...) +{ + gint i = 0; + GObject *object; + GList *objs = NULL, *tmp; + DestroyedObjectStruct *destroyed = g_slice_new0 (DestroyedObjectStruct); + + destroyed->object = G_OBJECT (object_to_unref); + g_object_weak_ref (G_OBJECT (object_to_unref), (GWeakNotify) weak_notify, + destroyed); + objs = g_list_prepend (objs, destroyed); + + if (first_object) { + va_list varargs; + + object = G_OBJECT (first_object); + + va_start (varargs, first_object); + while (object) { + destroyed = g_slice_new0 (DestroyedObjectStruct); + destroyed->object = object; + g_object_weak_ref (object, (GWeakNotify) weak_notify, destroyed); + objs = g_list_append (objs, destroyed); + object = va_arg (varargs, GObject *); + } + va_end (varargs); + } + gst_object_unref (object_to_unref); + + for (tmp = objs; tmp; tmp = tmp->next) { + fail_unless (((DestroyedObjectStruct *) tmp->data)->destroyed == TRUE, + "%p is not destroyed (object nb %i)", + ((DestroyedObjectStruct *) tmp->data)->object, i); + g_slice_free (DestroyedObjectStruct, tmp->data); + i++; + } + g_list_free (objs); + +} + +void +clean_bus (GstElement * element) +{ + GstBus *bus; + + bus = gst_element_get_bus (element); + gst_bus_set_flushing (bus, TRUE); + gst_object_unref (bus); +} + +GstValidatePadMonitor * +get_pad_monitor (GstPad * pad) +{ + return g_object_get_data ((GObject *) pad, "validate-monitor"); +} + +static GstStaticPadTemplate fake_demuxer_src_template = +GST_STATIC_PAD_TEMPLATE ("src%u", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("something") + ); + +static GstStaticPadTemplate fake_demuxer_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("something") + ); + +static GstFlowReturn +_demuxer_chain (GstPad * pad, GstObject * self, GstBuffer * buffer) +{ + gst_buffer_unref (buffer); + + return FAKE_DEMUXER (self)->return_value; +} + +static void +fake_demuxer_init (FakeDemuxer * self, gpointer * g_class) +{ + GstPad *pad; + GstElement *element = GST_ELEMENT (self); + GstPadTemplate *pad_template; + + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (g_class), "src%u"); + + pad = gst_pad_new_from_template (pad_template, "src0"); + gst_element_add_pad (element, pad); + + pad = gst_pad_new_from_template (pad_template, "src1"); + gst_element_add_pad (element, pad); + + pad = gst_pad_new_from_template (pad_template, "src2"); + gst_element_add_pad (element, pad); + + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (g_class), "sink"); + pad = gst_pad_new_from_template (pad_template, "sink"); + gst_element_add_pad (element, pad); + + self->return_value = GST_FLOW_OK; + + gst_pad_set_chain_function (pad, _demuxer_chain); +} + +static void +fake_demuxer_class_init (FakeDemuxerClass * self_class) +{ + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (self_class); + + gst_element_class_add_static_pad_template (gstelement_class, + &fake_demuxer_src_template); + gst_element_class_add_static_pad_template (gstelement_class, + &fake_demuxer_sink_template); + gst_element_class_set_static_metadata (gstelement_class, "Fake Demuxer", + "Demuxer", "Some demuxer", "Thibault Saunier"); +} + +GType +fake_demuxer_get_type (void) +{ + static gsize type = 0; + + if (g_once_init_enter (&type)) { + GType _type; + static const GTypeInfo info = { + sizeof (FakeDemuxerClass), + NULL, + NULL, + (GClassInitFunc) fake_demuxer_class_init, + NULL, + NULL, + sizeof (FakeDemuxer), + 0, + (GInstanceInitFunc) fake_demuxer_init, + }; + + _type = g_type_register_static (GST_TYPE_ELEMENT, "FakeDemuxer", &info, 0); + g_once_init_leave (&type, _type); + } + return type; +} + +GstElement * +fake_demuxer_new (void) +{ + return GST_ELEMENT (g_object_new (FAKE_DEMUXER_TYPE, NULL)); +} + +GstElement * +create_and_monitor_element (const gchar * factoryname, const gchar * name, + GstValidateRunner * runner) +{ + GstElement *element; + GstValidateMonitor *monitor; + + element = gst_element_factory_make (factoryname, name); + if (runner) { + monitor = + gst_validate_monitor_factory_create (GST_OBJECT (element), runner, + NULL); + fail_unless (GST_IS_VALIDATE_ELEMENT_MONITOR (monitor)); + } + + return element; +} + +void +free_element_monitor (GstElement * element) +{ + GstValidateMonitor *monitor; + monitor = + (GstValidateMonitor *) g_object_get_data (G_OBJECT (element), + "validate-monitor"); + + g_object_unref (G_OBJECT (monitor)); +} + +/****************************************** + * Fake decoder * + ******************************************/ +static GstStaticPadTemplate fake_decoder_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("video/x-raw") + ); + +static GstStaticPadTemplate fake_decoder_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-raw") + ); + +static GstFlowReturn +_decoder_chain (GstPad * pad, GstObject * self, GstBuffer * buffer) +{ + gst_buffer_unref (buffer); + + return FAKE_DECODER (self)->return_value; +} + +static void +fake_decoder_init (FakeDecoder * self, gpointer * g_class) +{ + GstPad *pad; + GstElement *element = GST_ELEMENT (self); + GstPadTemplate *pad_template; + + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (g_class), "src"); + pad = gst_pad_new_from_template (pad_template, "src"); + gst_element_add_pad (element, pad); + + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (g_class), "sink"); + pad = gst_pad_new_from_template (pad_template, "sink"); + gst_element_add_pad (element, pad); + + self->return_value = GST_FLOW_OK; + + gst_pad_set_chain_function (pad, _decoder_chain); +} + +static void +fake_decoder_class_init (FakeDecoderClass * self_class) +{ + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (self_class); + + gst_element_class_add_static_pad_template (gstelement_class, + &fake_decoder_src_template); + gst_element_class_add_static_pad_template (gstelement_class, + &fake_decoder_sink_template); + gst_element_class_set_static_metadata (gstelement_class, "Fake Decoder", + "Decoder", "Some decoder", "Thibault Saunier"); +} + +GType +fake_decoder_get_type (void) +{ + static gsize type = 0; + + if (g_once_init_enter (&type)) { + GType _type; + static const GTypeInfo info = { + sizeof (FakeDecoderClass), + NULL, + NULL, + (GClassInitFunc) fake_decoder_class_init, + NULL, + NULL, + sizeof (FakeDecoder), + 0, + (GInstanceInitFunc) fake_decoder_init, + }; + + _type = g_type_register_static (GST_TYPE_ELEMENT, "FakeDecoder", &info, 0); + g_once_init_leave (&type, _type); + } + return type; +} + +GstElement * +fake_decoder_new (void) +{ + return GST_ELEMENT (g_object_new (FAKE_DECODER_TYPE, NULL)); +} + +/****************************************** + * Fake mixer * + ******************************************/ +static GstElementClass *fake_mixer_parent_class = NULL; + +static GstStaticPadTemplate fake_mixer_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("something") + ); + +static GstStaticPadTemplate fake_mixer_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink_%u", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS ("something") + ); + +static gboolean +_mixer_event (GstPad * pad, GstObject * obj, GstEvent * event) +{ + FakeMixer *self = FAKE_MIXER (obj); + + switch (event->type) { + case GST_EVENT_STREAM_START: + if (g_atomic_int_compare_and_exchange (&self->sent_stream_start, FALSE, + TRUE)) { + return gst_pad_event_default (pad, obj, event); + } else { + gst_event_unref (event); + return TRUE; + } + case GST_EVENT_SEGMENT: + if (g_atomic_int_compare_and_exchange (&self->sent_segment, FALSE, TRUE)) { + return gst_pad_event_default (pad, obj, event); + } else { + gst_event_unref (event); + return TRUE; + } + default: + return gst_pad_event_default (pad, obj, event); + } +} + +static GstFlowReturn +_mixer_chain (GstPad * pad, GstObject * self, GstBuffer * buffer) +{ + gst_buffer_unref (buffer); + + return FAKE_MIXER (self)->return_value; +} + +static GstPad * +_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps) +{ + GstPad *pad; + GstPadTemplate *pad_template; + + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (G_OBJECT_GET_CLASS + (element)), "sink_%u"); + pad = gst_pad_new_from_template (pad_template, req_name); + + gst_pad_set_chain_function (pad, _mixer_chain); + gst_pad_set_event_function (pad, _mixer_event); + + gst_element_add_pad (element, pad); + + return pad; +} + +static void +fake_mixer_init (FakeMixer * self, FakeMixerClass * g_class) +{ + GstPad *pad; + GstElement *element = GST_ELEMENT (self); + GstPadTemplate *pad_template; + + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (g_class), "src"); + pad = gst_pad_new_from_template (pad_template, "src"); + gst_element_add_pad (element, pad); + + self->return_value = GST_FLOW_OK; +} + +static void +fake_mixer_class_init (FakeMixerClass * self_class) +{ + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (self_class); + + fake_mixer_parent_class = g_type_class_peek_parent (self_class); + + gst_element_class_add_static_pad_template (gstelement_class, + &fake_mixer_src_template); + gst_element_class_add_static_pad_template (gstelement_class, + &fake_mixer_sink_template); + gst_element_class_set_static_metadata (gstelement_class, "Fake mixer", + "Mixer", "Some mixer", "Thibault Saunier"); + + gstelement_class->request_new_pad = GST_DEBUG_FUNCPTR (_request_new_pad); +} + +GType +fake_mixer_get_type (void) +{ + static gsize type = 0; + + if (g_once_init_enter (&type)) { + GType _type; + static const GTypeInfo info = { + sizeof (FakeMixerClass), + NULL, + NULL, + (GClassInitFunc) fake_mixer_class_init, + NULL, + NULL, + sizeof (FakeMixer), + 0, + (GInstanceInitFunc) fake_mixer_init, + }; + + _type = g_type_register_static (GST_TYPE_ELEMENT, "FakeMixer", &info, 0); + g_once_init_leave (&type, _type); + } + return type; +} + +GstElement * +fake_mixer_new (void) +{ + return GST_ELEMENT (g_object_new (FAKE_MIXER_TYPE, NULL)); +} + +/****************************************** + * Fake Source * + *******************************************/ +static GstElementClass *fake_src_parent_class = NULL; + +static GstStaticPadTemplate fake_src_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("something") + ); + +static void +fake_src_init (FakeSrc * self, FakeSrcClass * g_class) +{ + GstPad *pad; + GstElement *element = GST_ELEMENT (self); + GstPadTemplate *pad_template; + + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (g_class), "src"); + pad = gst_pad_new_from_template (pad_template, "src"); + gst_element_add_pad (element, pad); + + self->return_value = GST_FLOW_OK; +} + +static void +fake_src_class_init (FakeSrcClass * self_class) +{ + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (self_class); + + fake_src_parent_class = g_type_class_peek_parent (self_class); + + gst_element_class_add_static_pad_template (gstelement_class, + &fake_src_src_template); + gst_element_class_set_static_metadata (gstelement_class, "Fake src", "Source", + "Some src", "Thibault Saunier"); +} + +GType +fake_src_get_type (void) +{ + static gsize type = 0; + + if (g_once_init_enter (&type)) { + GType _type; + static const GTypeInfo info = { + sizeof (FakeSrcClass), + NULL, + NULL, + (GClassInitFunc) fake_src_class_init, + NULL, + NULL, + sizeof (FakeSrc), + 0, + (GInstanceInitFunc) fake_src_init, + }; + + _type = g_type_register_static (GST_TYPE_ELEMENT, "FakeSrc", &info, 0); + g_once_init_leave (&type, _type); + } + return type; +} + +GstElement * +fake_src_new (void) +{ + return GST_ELEMENT (g_object_new (FAKE_SRC_TYPE, NULL)); +} + + +void +fake_elements_register (void) +{ + gst_element_register (NULL, "fakemixer", 0, FAKE_MIXER_TYPE); + gst_element_register (NULL, "fakedecoder", 0, FAKE_DECODER_TYPE); + gst_element_register (NULL, "fakedemuxer", 0, FAKE_DEMUXER_TYPE); + gst_element_register (NULL, "fakesrc2", 0, FAKE_SRC_TYPE); +} diff --git a/validate/tests/check/validate/test-utils.h b/validate/tests/check/validate/test-utils.h new file mode 100644 index 0000000000..11ae5b9e28 --- /dev/null +++ b/validate/tests/check/validate/test-utils.h @@ -0,0 +1,126 @@ +/* GstValidate + * Copyright (C) 2014 Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _GST_VALIDATE_TEST_UTILS +#define _GST_VALIDATE_TEST_UTILS + +#include "test-utils.h" +#include +#include +#include + +G_BEGIN_DECLS + +void check_destroyed (gpointer object_to_unref, gpointer first_object, ...) G_GNUC_NULL_TERMINATED; +GstValidateRunner * setup_runner (GstObject * object); +void clean_bus (GstElement *element); +GstValidatePadMonitor * get_pad_monitor (GstPad *pad); +GstElement * create_and_monitor_element (const gchar *factoryname, const gchar *name, GstValidateRunner *runner); +void free_element_monitor (GstElement *element); + +typedef struct { + GstElement parent; + + GstFlowReturn return_value; +} FakeDemuxer; + +typedef struct { + GstElementClass parent; +} FakeDemuxerClass; + +#define FAKE_DEMUXER_TYPE (fake_demuxer_get_type ()) +#define FAKE_DEMUXER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FAKE_DEMUXER_TYPE, FakeDemuxer)) +#define FAKE_DEMUXER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), FAKE_DEMUXER_TYPE, FakeDemuxerClass)) +#define IS_FAKE_DEMUXER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FAKE_DEMUXER_TYPE)) +#define IS_FAKE_DEMUXER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), FAKE_DEMUXER_TYPE)) +#define FAKE_DEMUXER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), FAKE_DEMUXER_TYPE, FakeDemuxerClass)) + +GType fake_demuxer_get_type (void); +GstElement * fake_demuxer_new (void); + +typedef struct { + GstElement parent; + + GstFlowReturn return_value; +} FakeDecoder; + +typedef struct { + GstElementClass parent; +} FakeDecoderClass; + +#define FAKE_DECODER_TYPE (fake_decoder_get_type ()) +#define FAKE_DECODER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FAKE_DECODER_TYPE, FakeDecoder)) +#define FAKE_DECODER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), FAKE_DECODER_TYPE, FakeDecoderClass)) +#define IS_FAKE_DECODER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FAKE_DECODER_TYPE)) +#define IS_FAKE_DECODER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), FAKE_DECODER_TYPE)) +#define FAKE_DECODER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), FAKE_DECODER_TYPE, FakeDecoderClass)) + +GType fake_decoder_get_type (void); +GstElement * fake_decoder_new (void); + +typedef struct { + GstElement parent; + + GstFlowReturn return_value; + + /* */ + gboolean sent_stream_start; + gboolean sent_segment; +} FakeMixer; + +typedef struct { + GstElementClass parent; +} FakeMixerClass; + +#define FAKE_MIXER_TYPE (fake_mixer_get_type ()) +#define FAKE_MIXER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FAKE_MIXER_TYPE, FakeMixer)) +#define FAKE_MIXER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), FAKE_MIXER_TYPE, FakeMixerClass)) +#define IS_FAKE_MIXER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FAKE_MIXER_TYPE)) +#define IS_FAKE_MIXER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), FAKE_MIXER_TYPE)) +#define FAKE_MIXER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), FAKE_MIXER_TYPE, FakeMixerClass)) + +GType fake_mixer_get_type (void); +GstElement * fake_mixer_new (void); + +typedef struct { + GstElement parent; + + GstFlowReturn return_value; + +} FakeSrc; + +typedef struct { + GstElementClass parent; +} FakeSrcClass; + +#define FAKE_SRC_TYPE (fake_src_get_type ()) +#define FAKE_SRC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FAKE_SRC_TYPE, FakeSrc)) +#define FAKE_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), FAKE_SRC_TYPE, FakeSrcClass)) +#define IS_FAKE_SRC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FAKE_SRC_TYPE)) +#define IS_FAKE_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), FAKE_SRC_TYPE)) +#define FAKE_SRC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), FAKE_SRC_TYPE, FakeSrcClass)) + +GType fake_src_get_type (void); +GstElement * fake_src_new (void); + +void fake_elements_register (void); + +G_END_DECLS + +#endif /* _GST_VALIDATE_TEST_UTILS */ diff --git a/validate/tests/check/validate/utilities.c b/validate/tests/check/validate/utilities.c new file mode 100644 index 0000000000..e6c420a626 --- /dev/null +++ b/validate/tests/check/validate/utilities.c @@ -0,0 +1,37 @@ +#include +#include +#include + +GST_START_TEST (test_resolve_variables) +{ + GstStructure *s1 = + gst_structure_from_string ("vars, a=(string)1, b=(string)2", NULL); + GstStructure *s2 = gst_structure_from_string ("test, n=\"$(a)/$(b)\"", NULL); + + gst_validate_structure_resolve_variables (NULL, s2, s1, 0); + fail_unless_equals_string (gst_structure_get_string (s2, "n"), "1/2"); + gst_structure_free (s1); + gst_structure_free (s2); +} + +GST_END_TEST; + +static Suite * +gst_validate_suite (void) +{ + Suite *s = suite_create ("utilities"); + TCase *tc_chain = tcase_create ("utilities"); + suite_add_tcase (s, tc_chain); + + if (atexit (gst_validate_deinit) != 0) { + GST_ERROR ("failed to set gst_validate_deinit as exit function"); + } + + gst_validate_init (); + tcase_add_test (tc_chain, test_resolve_variables); + gst_validate_deinit (); + + return s; +} + +GST_CHECK_MAIN (gst_validate); diff --git a/validate/tests/launcher_tests/check_set_prop_never_called_error.validatetest b/validate/tests/launcher_tests/check_set_prop_never_called_error.validatetest new file mode 100644 index 0000000000..8bb73e14f1 --- /dev/null +++ b/validate/tests/launcher_tests/check_set_prop_never_called_error.validatetest @@ -0,0 +1,9 @@ +meta, + args = { + "fakesrc num-buffers=1 ! fakesink", + }, + expected-issues = { + "expected-issue, issue-id=scenario::not-ended", + } + +set-property, target-element-factory-name=capsfilter, property-name=caps, property-value="video/x-raw,framerate=30/1,format=I420" \ No newline at end of file diff --git a/validate/tests/launcher_tests/check_set_props_and_time_props.validatetest b/validate/tests/launcher_tests/check_set_props_and_time_props.validatetest new file mode 100644 index 0000000000..2c49e8e760 --- /dev/null +++ b/validate/tests/launcher_tests/check_set_props_and_time_props.validatetest @@ -0,0 +1,54 @@ +meta, + handles-states=true, + args = { + "videotestsrc pattern=blue ! compositor name=c ! video/x-raw,width=100,height=100,framerate=10/1 ! $(videosink) videotestsrc pattern=green ! c.", + } + +# Start with the green stream on the top left corner and the blue on in the bottom right +set-timed-value-properties, timestamp=0.0, source-type=GstTriggerControlSource, binding-type=direct-absolute, + c.sink_0::xpos=50, + c.sink_0::ypos=50, + c.sink_0::width=50, + c.sink_0::height=50, + c.sink_1::xpos=0, + c.sink_1::ypos=0, + c.sink_1::width=50, + c.sink_1::height=50 + +# And invert it after 1 second of playback +set-timed-value-properties, timestamp=1.0, source-type=GstTriggerControlSource, binding-type=direct-absolute, + c.sink_0::xpos=0, + c.sink_0::ypos=0, + c.sink_1::xpos=50, + c.sink_1::ypos=50 + +play +crank-clock, expected-time=0.0 +check-properties, + c.sink_0::xpos=50, + c.sink_0::ypos=50, + c.sink_1::xpos=0, + c.sink_1::ypos=0 + +crank-clock, repeat=5 +check-position, expected-position=0.5 +check-properties, + c.sink_0::xpos=50, + c.sink_0::ypos=50, + c.sink_1::xpos=0, + c.sink_1::ypos=0 + +crank-clock, repeat=5 +check-position, expected-position=1.0 +check-properties, + c.sink_0::xpos=0, + c.sink_0::ypos=0, + c.sink_1::xpos=50, + c.sink_1::ypos=50 + +set-properties, c.sink_0::xpos=50, c.sink_0::ypos=50, c.sink_1::xpos=0, c.sink_1::ypos=0 +check-properties, c.sink_0::xpos=50, c.sink_0::ypos=50, c.sink_1::xpos=0, c.sink_1::ypos=0 + +set-properties, c::latency=50.0 +check-properties, c::latency=50.0 +stop \ No newline at end of file diff --git a/validate/tests/launcher_tests/foreach.validatetest b/validate/tests/launcher_tests/foreach.validatetest new file mode 100644 index 0000000000..a5201ffa9e --- /dev/null +++ b/validate/tests/launcher_tests/foreach.validatetest @@ -0,0 +1,49 @@ +meta, + handles-states=true, + args = { + "videotestsrc pattern=ball animation-mode=frames num-buffers=30 ! video/x-raw,framerate=10/1 ! $(videosink) name=sink sync=true", + }, + expected-issues = { + [ + expected-issue, + level=critical, + issue-id=scenario::execution-error, + details="Pipeline position doesn.t match expectations got 0:00:00.100000000 instead of.*", + ], + [ + expected-issue, + level=critical, + issue-id=scenario::execution-error, + details="Pipeline position doesn.t match expectations got 0:00:00.200000000 instead of.*", + ], + } + +pause; + +foreach, n=[0, 2], + actions = { + [seek, start="$(position)+0.1", flags="accurate+flush"], + [check-position, expected-position="expr($(n)*0.01)"], # expected to fail + } + +priv_check-action-type-calls, type=seek, n=2 +priv_check-action-type-calls, type=check-position, n=2 + +foreach, n=[0, 6], actions = { + [seek, start="$(position)+0.1", flags="accurate+flush"], + [check-position, expected-position="expr((3 + $(n)) * 0.1)"], +} + +priv_check-action-type-calls, type=seek, n=8 +priv_check-action-type-calls, type=check-position, n=8 +check-position, expected-position=0.8 + +foreach, n=[9, 11], actions = { + [seek, start="$(position)+0.1", flags="accurate+flush"], + [check-position, expected-position="expr($(n)*0.1)"], +} +priv_check-action-type-calls, type=seek, n=10 +# We called it once manually +priv_check-action-type-calls, type=check-position, n=11 +check-position, expected-position=1.0 +stop diff --git a/validate/tests/launcher_tests/foreach/flow-expectations/log-sink-sink-expected b/validate/tests/launcher_tests/foreach/flow-expectations/log-sink-sink-expected new file mode 100644 index 0000000000..f525c3c6e6 --- /dev/null +++ b/validate/tests/launcher_tests/foreach/flow-expectations/log-sink-sink-expected @@ -0,0 +1,24 @@ +event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE, group-id=(uint)1; +event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE, group-id=(uint)1; +event caps: video/x-raw, format=(string)AYUV64, width=(int)320, height=(int)240, framerate=(fraction)10/1, multiview-mode=(string)mono, pixel-aspect-ratio=(fraction)1/1, interlace-mode=(string)progressive; +event caps: video/x-raw, format=(string)AYUV64, width=(int)320, height=(int)240, framerate=(fraction)10/1, multiview-mode=(string)mono, pixel-aspect-ratio=(fraction)1/1, interlace-mode=(string)progressive; +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 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 +buffer: checksum=5d4a9a9aa2038170a66bb2c675a16672fe70efbe, pts=0:00:00.000000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +buffer: checksum=5d4a9a9aa2038170a66bb2c675a16672fe70efbe, pts=0:00:00.000000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +event flush-start: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.100000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.100000000, base=0:00:00.000000000, position=0:00:00.100000000 +event segment: format=TIME, start=0:00:00.100000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.100000000, base=0:00:00.000000000, position=0:00:00.100000000 +buffer: checksum=ace920a5c387c5d216c7bf4fdc83df6ac9d2656e, pts=0:00:00.100000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +buffer: checksum=ace920a5c387c5d216c7bf4fdc83df6ac9d2656e, pts=0:00:00.100000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +event flush-start: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.200000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.200000000, base=0:00:00.000000000, position=0:00:00.200000000 +event segment: format=TIME, start=0:00:00.200000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.200000000, base=0:00:00.000000000, position=0:00:00.200000000 +buffer: checksum=b4a5b43f70ad1a1adb1f5e414b39d6bfb5718373, pts=0:00:00.200000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +buffer: checksum=b4a5b43f70ad1a1adb1f5e414b39d6bfb5718373, pts=0:00:00.200000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta diff --git a/validate/tests/launcher_tests/foreach_array.validatetest b/validate/tests/launcher_tests/foreach_array.validatetest new file mode 100644 index 0000000000..06780b510b --- /dev/null +++ b/validate/tests/launcher_tests/foreach_array.validatetest @@ -0,0 +1,33 @@ +meta, + handles-states=true, + args = { + "videotestsrc pattern=ball name=s ! fakesink", + } + +foreach, pattern=, + actions = { + [set-properties, s::pattern="$(pattern)"], + } + +priv_check-action-type-calls, type=set-properties, n=2 + +check-properties, s::pattern="Blue" + +foreach, pattern=< + # We can also pass int values (which works for enums) + 1, 3, 5, # green + >, + actions = { + [set-properties, s::pattern="$(pattern)"], + } + +priv_check-action-type-calls, type=set-properties, n=5 +check-properties, s::pattern="Green" + +foreach, pattern=, + actions = { + [set-properties, s::pattern="$(pattern)"], + } +priv_check-action-type-calls, type=set-properties, n=8 +check-properties, s::pattern="100\%\ Black" +stop diff --git a/validate/tests/launcher_tests/foreach_deep.validatetest b/validate/tests/launcher_tests/foreach_deep.validatetest new file mode 100644 index 0000000000..b54ea0b3da --- /dev/null +++ b/validate/tests/launcher_tests/foreach_deep.validatetest @@ -0,0 +1,80 @@ +meta, + handles-states=true, + args = { + "videotestsrc pattern=ball animation-mode=frames num-buffers=30 ! video/x-raw,framerate=10/1 ! $(videosink) name=sink sync=true", + }, + expected-issues = { + [ + expected-issue, + level=critical, + issue-id=scenario::execution-error, + details="Pipeline position doesn.t match expectations got 0:00:00.100000000 instead of.*", + ], + [ + expected-issue, + level=critical, + issue-id=scenario::execution-error, + details="Pipeline position doesn.t match expectations got 0:00:00.200000000 instead of.*", + ], + [ + expected-issue, + level=critical, + issue-id=scenario::execution-error, + details="Expected subaction level 4, got 3", + ], + [ + expected-issue, + level=critical, + issue-id=scenario::execution-error, + details="Expected subaction level 4, got 3", + ], + [ + expected-issue, + level=critical, + issue-id=scenario::execution-error, + details="Expected subaction level 5, got 4", + ], + [ + expected-issue, + level=critical, + issue-id=scenario::execution-error, + details="Expected subaction level 5, got 4", + ], + } + +pause; + + +foreach, n=[0, 2], + actions={ + [seek, start="$(position)+0.1", flags="accurate+flush"], + [check-position, expected-position="expr($(n)*0.01)"], # Expected failling subaction! + } + +priv_check-action-type-calls, type=seek, n=2 +priv_check-action-type-calls, type=check-position, n=2 + +foreach, i=[0, 2], + actions={ + [seek, start="$(position)+0.1", flags="accurate+flush"], + [priv_check-subaction-level, level=1], + [foreach, j=[0, 1], + actions={ + [priv_check-subaction-level, level=2], + [foreach, k=[0, 1], + actions={ + [priv_check-subaction-level, level=4], # Failling... twice + [priv_check-subaction-level, level=3], + [foreach, l=[0, 1], + actions={ + [priv_check-subaction-level, level=4], + [priv_check-subaction-level, level=5], # Failling... twice + }, + ], + }, + ], + }, + ], + } +priv_check-action-type-calls, type=seek, n=4 +stop diff --git a/validate/tests/launcher_tests/foreach_repeat.validatetest b/validate/tests/launcher_tests/foreach_repeat.validatetest new file mode 100644 index 0000000000..94ccc92d9d --- /dev/null +++ b/validate/tests/launcher_tests/foreach_repeat.validatetest @@ -0,0 +1,31 @@ +meta, + handles-states=true, + args = { + "videotestsrc name=src pattern=ball animation-mode=frames num-buffers=30 ! video/x-raw,framerate=10/1 ! $(videosink) name=sink sync=true", + } + +pause; + +foreach, repeat="max(1, 2)", + actions = { + "seek, start=\"$(position)+0.1\", flags=\"accurate+flush\"", + "check-position, expected-position=\"expr((1+$(repeat))*0.1)\"", + } + +priv_check-action-type-calls, type=seek, n=2 +priv_check-action-type-calls, type=check-position, n=2 + +foreach, + repeat=2, + pattern=[0, 10, 5], + actions = { + "set-properties, src::horizontal-speed=\"$(pattern)\"", + "check-properties, src::horizontal-speed=\"$(pattern)\"", + } + +check-properties, src::horizontal-speed=5 +priv_check-action-type-calls, type=set-properties, n=4 +priv_check-action-type-calls, type=check-properties, n=5 +priv_check-action-type-calls, type=seek, n=2 +priv_check-action-type-calls, type=check-position, n=2 +stop diff --git a/validate/tests/launcher_tests/meson.build b/validate/tests/launcher_tests/meson.build new file mode 100644 index 0000000000..9d5382e51c --- /dev/null +++ b/validate/tests/launcher_tests/meson.build @@ -0,0 +1,15 @@ +if launcher.found() + test_name = 'validate/launcher_tests' + + env = environment() + env.set('GST_STATE_IGNORE_ELEMENTS', '') + env.set('GST_PLUGIN_SYSTEM_PATH_1_0', '') + env.set('GST_PLUGIN_PATH_1_0', [meson.build_root()] + pluginsdirs) + env.set('GST_REGISTRY', '@0@/@1@.registry'.format(meson.current_build_dir(), test_name)) + env.set('GST_PLUGIN_SCANNER_1_0', gst_plugin_scanner_path) + + test(test_name, launcher, args: ['-o', meson.build_root() + '/validate-launcher-output/', + meson.current_source_dir() + '/test_validate.py', '--validate-tools-path', + join_paths(meson.current_build_dir(), '..', '..', 'tools')], + env: env) +endif diff --git a/validate/tests/launcher_tests/not_negotiated.accept_caps_failure.validatetest b/validate/tests/launcher_tests/not_negotiated.accept_caps_failure.validatetest new file mode 100644 index 0000000000..aa8777cabf --- /dev/null +++ b/validate/tests/launcher_tests/not_negotiated.accept_caps_failure.validatetest @@ -0,0 +1,7 @@ +meta, + args = { + "audiotestsrc ! capsfilter caps=\"audio/x-raw,channels=2,channel-mask=(bitmask)0x67\" ! audioconvert ! capsfilter caps=\"audio/x-raw,channels=6,channel-mask=(bitmask)0x32\" name=capsfilter ! fakesink", + }, + expected-issues = { + "expected-issue, level=critical, summary=\"a NOT NEGOTIATED message has been posted on the bus.\", details=\".*Caps negotiation failed at pad.*capsfilter:sink.*as it refused caps:.*\"", + } diff --git a/validate/tests/launcher_tests/run_command_with_envvars.validatetest b/validate/tests/launcher_tests/run_command_with_envvars.validatetest new file mode 100644 index 0000000000..804515eb65 --- /dev/null +++ b/validate/tests/launcher_tests/run_command_with_envvars.validatetest @@ -0,0 +1,17 @@ +meta, + args = { + "videotestsrc ! fakesink sync=true", + }, + handles-states=true, + ignore-eos=true + +run-command, + argv={ + python3, -c, "import os; assert os.environ['ENVVAR_CHECK'] == 'set'", + }, + env=[ + vars, + ENVVAR_CHECK="set", + ] + +stop diff --git a/validate/tests/launcher_tests/simple_interlaced_action.validatetest b/validate/tests/launcher_tests/simple_interlaced_action.validatetest new file mode 100644 index 0000000000..6ae4dae65e --- /dev/null +++ b/validate/tests/launcher_tests/simple_interlaced_action.validatetest @@ -0,0 +1,12 @@ +meta, + handles-states=true, + args = { + "appsrc name=src ! typefind ! fakesink", + } + +# Let push ourself into the pipeline :-) +appsrc-push, file-name="$(test_dir)/$(test_name).validatetest", target-element-name=src +priv_check-action-type-calls, type=appsrc-push, n=1 +appsrc-eos, target-element-name=src +pause; +stop diff --git a/validate/tests/launcher_tests/simple_repeat.validatetest b/validate/tests/launcher_tests/simple_repeat.validatetest new file mode 100644 index 0000000000..941a06e26c --- /dev/null +++ b/validate/tests/launcher_tests/simple_repeat.validatetest @@ -0,0 +1,15 @@ +meta, + handles-states=true, + args = { + "videotestsrc pattern=ball animation-mode=frames num-buffers=30 ! video/x-raw,framerate=10/1 ! $(videosink) name=sink sync=true", + }, + configs = { + "$(validateflow), pad=sink:sink, buffers-checksum=true", + } + +pause; +seek, start="$(position)+0.1", repeat=10, flags="accurate+flush" +priv_check-action-type-calls, type=seek, n=10 +check-position, expected-position=1.0 +priv_check-action-type-calls, type=check-position, n=1 +stop \ No newline at end of file diff --git a/validate/tests/launcher_tests/simple_repeat/flow-expectations/log-sink-sink-expected b/validate/tests/launcher_tests/simple_repeat/flow-expectations/log-sink-sink-expected new file mode 100644 index 0000000000..112ac9aa64 --- /dev/null +++ b/validate/tests/launcher_tests/simple_repeat/flow-expectations/log-sink-sink-expected @@ -0,0 +1,88 @@ +event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE, group-id=(uint)1; +event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE, group-id=(uint)1; +event caps: video/x-raw, format=(string)AYUV64, framerate=(fraction)10/1, height=(int)240, interlace-mode=(string)progressive, multiview-mode=(string)mono, pixel-aspect-ratio=(fraction)1/1, width=(int)320; +event caps: video/x-raw, format=(string)AYUV64, framerate=(fraction)10/1, height=(int)240, interlace-mode=(string)progressive, multiview-mode=(string)mono, pixel-aspect-ratio=(fraction)1/1, width=(int)320; +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 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 +buffer: checksum=5d4a9a9aa2038170a66bb2c675a16672fe70efbe, pts=0:00:00.000000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +buffer: checksum=5d4a9a9aa2038170a66bb2c675a16672fe70efbe, pts=0:00:00.000000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +event flush-start: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.100000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.100000000, base=0:00:00.000000000, position=0:00:00.100000000 +event segment: format=TIME, start=0:00:00.100000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.100000000, base=0:00:00.000000000, position=0:00:00.100000000 +buffer: checksum=ace920a5c387c5d216c7bf4fdc83df6ac9d2656e, pts=0:00:00.100000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +buffer: checksum=ace920a5c387c5d216c7bf4fdc83df6ac9d2656e, pts=0:00:00.100000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +event flush-start: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.200000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.200000000, base=0:00:00.000000000, position=0:00:00.200000000 +event segment: format=TIME, start=0:00:00.200000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.200000000, base=0:00:00.000000000, position=0:00:00.200000000 +buffer: checksum=b4a5b43f70ad1a1adb1f5e414b39d6bfb5718373, pts=0:00:00.200000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +buffer: checksum=b4a5b43f70ad1a1adb1f5e414b39d6bfb5718373, pts=0:00:00.200000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +event flush-start: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.300000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.300000000, base=0:00:00.000000000, position=0:00:00.300000000 +event segment: format=TIME, start=0:00:00.300000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.300000000, base=0:00:00.000000000, position=0:00:00.300000000 +buffer: checksum=9dd6d94a11092f698af7c1a0771ad23445659e2f, pts=0:00:00.300000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +buffer: checksum=9dd6d94a11092f698af7c1a0771ad23445659e2f, pts=0:00:00.300000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +event flush-start: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.400000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.400000000, base=0:00:00.000000000, position=0:00:00.400000000 +event segment: format=TIME, start=0:00:00.400000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.400000000, base=0:00:00.000000000, position=0:00:00.400000000 +buffer: checksum=319d16fff322be3938dacf51b06d9fe6f8252e13, pts=0:00:00.400000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +buffer: checksum=319d16fff322be3938dacf51b06d9fe6f8252e13, pts=0:00:00.400000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +event flush-start: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.500000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.500000000, base=0:00:00.000000000, position=0:00:00.500000000 +event segment: format=TIME, start=0:00:00.500000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.500000000, base=0:00:00.000000000, position=0:00:00.500000000 +buffer: checksum=f8172402fe1df5b501044463234cdcd85d912257, pts=0:00:00.500000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +buffer: checksum=f8172402fe1df5b501044463234cdcd85d912257, pts=0:00:00.500000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +event flush-start: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.600000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.600000000, base=0:00:00.000000000, position=0:00:00.600000000 +event segment: format=TIME, start=0:00:00.600000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.600000000, base=0:00:00.000000000, position=0:00:00.600000000 +buffer: checksum=4b19a11f95babc8bffccafde3a12d72de6d160e0, pts=0:00:00.600000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +buffer: checksum=4b19a11f95babc8bffccafde3a12d72de6d160e0, pts=0:00:00.600000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +event flush-start: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.700000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.700000000, base=0:00:00.000000000, position=0:00:00.700000000 +event segment: format=TIME, start=0:00:00.700000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.700000000, base=0:00:00.000000000, position=0:00:00.700000000 +buffer: checksum=421d394aad2e819d1c64f795b8370b7fae86d67f, pts=0:00:00.700000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +buffer: checksum=421d394aad2e819d1c64f795b8370b7fae86d67f, pts=0:00:00.700000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +event flush-start: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.800000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.800000000, base=0:00:00.000000000, position=0:00:00.800000000 +event segment: format=TIME, start=0:00:00.800000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.800000000, base=0:00:00.000000000, position=0:00:00.800000000 +buffer: checksum=eaaf9c6b1c138aceab33003ed174782cfc9e49b0, pts=0:00:00.800000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +buffer: checksum=eaaf9c6b1c138aceab33003ed174782cfc9e49b0, pts=0:00:00.800000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +event flush-start: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.900000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.900000000, base=0:00:00.000000000, position=0:00:00.900000000 +event segment: format=TIME, start=0:00:00.900000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.900000000, base=0:00:00.000000000, position=0:00:00.900000000 +buffer: checksum=d5f276bf018900cd1f5e56110d5548aab1a554a6, pts=0:00:00.900000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +buffer: checksum=d5f276bf018900cd1f5e56110d5548aab1a554a6, pts=0:00:00.900000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +event flush-start: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:01.000000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:01.000000000, base=0:00:00.000000000, position=0:00:01.000000000 +event segment: format=TIME, start=0:00:01.000000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:01.000000000, base=0:00:00.000000000, position=0:00:01.000000000 +buffer: checksum=2d539cab006ef26cb022f0bc3277664ad7d34786, pts=0:00:01.000000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +buffer: checksum=2d539cab006ef26cb022f0bc3277664ad7d34786, pts=0:00:01.000000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta diff --git a/validate/tests/launcher_tests/test_validate.py b/validate/tests/launcher_tests/test_validate.py new file mode 100644 index 0000000000..bad3275344 --- /dev/null +++ b/validate/tests/launcher_tests/test_validate.py @@ -0,0 +1,64 @@ +# -*- Mode: Python -*- vi:si:et:sw=4:sts=4:ts=4:syntax=python +# +# Copyright (c) 2016,Thibault Saunier +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +""" +The GstValidate default testsuite +""" +import os +from launcher.apps.gstvalidate import GstValidateSimpleTestsGenerator + +TEST_MANAGER = "validate" + + +def get_pipelines(test_manager): + return [("not_negotiated.accept_caps_failure", + "audiotestsrc ! audio/x-raw,channels=2,channel-mask='(bitmask)0x67' " + "! audioconvert ! capsfilter caps=audio/x-raw,channels=6,channel-mask='(bitmask)0x32' " + " name=capsfilter ! fakesink", + {"expected-issues": [ + {'returncode': 18}, + {'level': 'critical', 'summary': 'a NOT NEGOTIATED message has been posted on the bus.', + 'details': r'.*Caps negotiation failed at pad.*capsfilter:sink.*as it refused caps:.*'}]}), + ("not_negotiated.caps_query_failure", + "\( \( audiotestsrc \) ! input-selector name=i \) ! capsfilter name=capsfilter caps=video/x-raw ! fakesink", + {"expected-issues": [ + {'returncode': 18}, + {'level': 'critical', 'summary': 'a NOT NEGOTIATED message has been posted on the bus.', + 'details': 'Caps negotiation failed starting from pad \'capsfilter:sink\' as the ' + 'QUERY_CAPS returned EMPTY caps for the following possible reasons:'}]})] + + +def setup_tests(test_manager, options): + testsuite_dir = os.path.realpath(os.path.join(os.path.dirname(__file__))) + + print("Setting up tests to test GstValidate") + # No restriction about scenarios that are potentially used + valid_scenarios = ["play_15s"] + test_manager.add_scenarios(valid_scenarios) + test_manager.add_generators(test_manager.GstValidatePipelineTestsGenerator + ("test_validate", test_manager, + pipelines_descriptions=get_pipelines(test_manager), + valid_scenarios=valid_scenarios)) + + test_manager.add_generators( + GstValidateSimpleTestsGenerator("simple", test_manager, + os.path.join(testsuite_dir)) + ) + + return True diff --git a/validate/tests/meson.build b/validate/tests/meson.build new file mode 100644 index 0000000000..7704328e25 --- /dev/null +++ b/validate/tests/meson.build @@ -0,0 +1,18 @@ +pluginsdirs = [ ] +if gst_dep.type_name() == 'pkgconfig' + pbase = dependency('gstreamer-plugins-base-' + apiversion) + + pluginsdirs = [gst_dep.get_pkgconfig_variable('pluginsdir'), + pbase.get_pkgconfig_variable('pluginsdir')] + gst_plugin_scanner_dir = gst_dep.get_pkgconfig_variable('pluginscannerdir') +else + gst_plugin_scanner_dir = subproject('gstreamer').get_variable('gst_scanner_dir') +endif +gst_plugin_scanner_path = join_paths(gst_plugin_scanner_dir, 'gst-plugin-scanner') + +# FIXME: make check work on windows +if host_machine.system() != 'windows' and gst_check_dep.found() + subdir('check') +endif + +subdir('launcher_tests') diff --git a/validate/tools/gst-validate-analyze b/validate/tools/gst-validate-analyze new file mode 100755 index 0000000000..eb8627f813 --- /dev/null +++ b/validate/tools/gst-validate-analyze @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2015, Edward Hervey +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +import os +import sys +import xml.etree.cElementTree + + +def extract_info(xmlfile): + e = xml.etree.cElementTree.parse(xmlfile).getroot() + r = {} + for i in e: + r[(i.get("classname"), i.get("name"))] = i + return r + + +if "__main__" == __name__: + if len(sys.argv) < 2: + print("Usage : %s [] " % sys.argv[0]) + if len(sys.argv) == 3: + oldfile = extract_info(sys.argv[1]) + newfile = extract_info(sys.argv[2]) + else: + oldfile = [] + newfile = extract_info(sys.argv[1]) + + # new failures (pass in old run, fail in new run) + newfail = [] + # new fixes (fail in old run, pass in new run) + newfix = [] + # tests that are still failing + stillfail = [] + # tests that are still failling but for a different reason + failchange = [] + + # failed tests sorted by reason + reasons = {} + + # all files + allfiles = [] + + # failed tests sorted by file + failedfiles = {} + + if oldfile: + # tests that weren't present in old run + newtests = [x for x in newfile.keys() if x not in oldfile] + # tests that are no longer present in new run + gonetests = [x for x in oldfile.keys() if x not in newfile] + + # go over new tests + for k, v in newfile.iteritems(): + tn, fn = k + if not fn in allfiles: + allfiles.append(fn) + newf = v.findall("error") + if newf: + # extract the failure reason + r = newf[0].get("message") + if "Application returned 18" in r or "Application returned -5" in r: + rs = r.split('[')[1].split(']')[0].split(',') + for la in rs: + la = la.strip() + if la not in reasons: + reasons[la] = [] + reasons[la].append(k) + if fn not in failedfiles: + failedfiles[fn] = [] + failedfiles[fn].append((tn, r)) + + if k in oldfile: + oldone = oldfile.get(k) + + # compare failures + oldf = oldone.findall("error") + if newf and not oldf: + newfail.append(k) + if oldf and not newf: + newfix.append(k) + if oldf and newf: + stillfail.append(k) + a = oldf[0] + b = newf[0] + print a, b + # check if the failure reasons are the same + if a.get("type") != b.get("type"): + failchange.append(k) + elif a.get("message") != b.get("message"): + failchange.append(k) + + if newfail: + print("New failures", len(newfail)) + newfail.sort() + for i in newfail: + print " %s : %s" % (i[0], i[1]) + f = newfile[i].find("error") + print " ", f.get("type"), f.get("message") + print + + if newfix: + print "New fixes", len(newfix) + newfix.sort() + for i in newfix: + print " %s : %s" % (i[0], i[1]) + print + + if failchange: + print "Failure changes", len(failchange) + failchange.sort() + for i in failchange: + print " %s : %s" % (i[0], i[1]) + oldt = oldfile[i].find("error").get("type") + newt = newfile[i].find("error").get("type") + if oldt != newt: + print " Went from '%s' to '%s'" % (oldt, newt) + print " Previous message :", oldfile[i].find("error").get("message") + print " New message :", newfile[i].find("error").get("message") + print + + for k, v in reasons.iteritems(): + print "Failure type : ", k, len(v) + v.sort() + for i in v: + print " %s : %s" % (i[0], i[1]) + print + + nofailfiles = sorted([fn for fn in allfiles if fn not in failedfiles]) + if nofailfiles: + print "Files without failures", len(nofailfiles) + for f in nofailfiles: + print " ", f + print + + for k, v in failedfiles.iteritems(): + print "Failed File :", k + for i in v: + print " %s : %s" % (i[0], i[1]) diff --git a/validate/tools/gst-validate-images-check.c b/validate/tools/gst-validate-images-check.c new file mode 100644 index 0000000000..3e1625ccd0 --- /dev/null +++ b/validate/tools/gst-validate-images-check.c @@ -0,0 +1,122 @@ +/* GStreamer + * + * Copyright (C) 2014 Mathieu Duponchelle + * Copyright (C) 2015 Raspberry Pi Foundation + * Author: Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "../gst-libs/gst/video/gstvalidatessim.h" + +#include +#include +#include +#include /* for LC_ALL */ + +int +main (int argc, char **argv) +{ + GstValidateSsim *ssim; + gint rep_err, ret = 0; + GError *err = NULL; + GstValidateRunner *runner = NULL; + GOptionContext *ctx; + gchar *outfolder = NULL; + gfloat mssim = 0, lowest = 1, highest = -1; + gdouble min_avg_similarity = 0.95, min_lowest_similarity = -1.0; + + GOptionEntry options[] = { + {"min-avg-similarity", 'a', 0, G_OPTION_ARG_DOUBLE, + &min_avg_similarity, + "The minimum average similarity under which we consider" + " the test as failing", + NULL}, + {"min-lowest-similarity", 'l', 0, G_OPTION_ARG_DOUBLE, + &min_lowest_similarity, + "The minimum 'lowest' similarity under which we consider" + " the test as failing", + NULL}, + {"result-output-folder", 'r', 0, G_OPTION_ARG_FILENAME, + &outfolder, + "The folder in which to store resulting grey scale images" + " when the test failed. In that folder you will find" + " images with the structural difference between" + " the reference frame and the failed one", + NULL}, + {NULL} + }; + + setlocale (LC_ALL, ""); + + g_set_prgname ("gst-validate-images-check-" GST_API_VERSION); + ctx = g_option_context_new ("/reference/file/path /compared/file/path"); + + g_option_context_set_summary (ctx, + "The gst-validate-images-check calculates SSIM (Structural SIMilarity)" + " index for the images. And according to min-lowest-similarity and" + " min-avg-similarity, it will consider the images similar enough" + " or report critical issues in the GstValidate reporting system"); + g_option_context_add_main_entries (ctx, options, NULL); + + if (!g_option_context_parse (ctx, &argc, &argv, &err)) { + g_printerr ("Error initializing: %s\n", err->message); + g_option_context_free (ctx); + g_clear_error (&err); + return -1; + } + + if (argc != 3) { + gchar *help = g_option_context_get_help (ctx, FALSE, NULL); + g_printerr ("%s", help); + g_free (help); + g_option_context_free (ctx); + + return -1; + } + + gst_init (&argc, &argv); + gst_validate_init (); + + runner = gst_validate_runner_new (); + ssim = + gst_validate_ssim_new (runner, min_avg_similarity, min_lowest_similarity, + 0, 1); + + gst_validate_ssim_compare_image_files (ssim, argv[1], argv[2], &mssim, + &lowest, &highest, outfolder); + + if (!g_file_test (argv[1], G_FILE_TEST_IS_DIR)) { + gst_validate_printf (ssim, "Compared %s with %s, average: %f, Min %f\n", + argv[1], argv[2], mssim, lowest); + } + + rep_err = gst_validate_runner_exit (runner, TRUE); + if (ret == 0) { + ret = rep_err; + if (rep_err != 0) + gst_validate_printf (NULL, "Returning %d as error where found", rep_err); + } + + g_object_unref (ssim); + g_object_unref (runner); + gst_validate_deinit (); + + gst_validate_printf (NULL, "\n=======> Test %s (Return value: %i)\n\n", + ret == 0 ? "PASSED" : "FAILED", ret); + + return ret; +} diff --git a/validate/tools/gst-validate-launcher.in b/validate/tools/gst-validate-launcher.in new file mode 100755 index 0000000000..f37304bdc1 --- /dev/null +++ b/validate/tools/gst-validate-launcher.in @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2014,Thibault Saunier +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +import os +import subprocess +import sys + +LIBDIR = r'@LIBDIR@' +BUILDDIR = r'@BUILDDIR@' +SRCDIR = r'@SRCDIR@' + + +def _add_gst_launcher_path(): + f = os.path.abspath(__file__) + if f.startswith(BUILDDIR): + # Make sure to have the configured config.py in the python path + sys.path.insert(0, os.path.abspath(os.path.join(BUILDDIR, ".."))) + root = os.path.abspath(os.path.join(SRCDIR, "../")) + else: + root = os.path.join(LIBDIR, 'gst-validate-launcher', 'python') + + sys.path.insert(0, root) + return os.path.join(root, "launcher") + + +if "__main__" == __name__: + libsdir = _add_gst_launcher_path() + from launcher.main import main + run_profile = os.environ.get('GST_VALIDATE_LAUNCHER_PROFILING', False) + if run_profile: + import cProfile + prof = cProfile.Profile() + try: + res = prof.runcall(main, libsdir) + finally: + prof.dump_stats('gst-validate-launcher-runstats') + exit(res) + + exit(main(libsdir)) diff --git a/validate/tools/gst-validate-media-check.c b/validate/tools/gst-validate-media-check.c new file mode 100644 index 0000000000..f357ac4eb1 --- /dev/null +++ b/validate/tools/gst-validate-media-check.c @@ -0,0 +1,187 @@ +/* GStreamer + * + * Copyright (C) 2013 Collabora Ltd. + * Author: Thiago Sousa Santos + * + * gst-validate-media-check.c - Media Check CLI tool + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include /* for LC_ALL */ + +int +main (int argc, gchar ** argv) +{ + GOptionContext *ctx; + + guint ret = 0; + GError *err = NULL; + gboolean full = FALSE; + gboolean skip_parsers = FALSE; + gchar *output_file = NULL; + gchar *expected_file = NULL; + gchar *output = NULL; + GstValidateMediaDescriptorWriterFlags writer_flags = + GST_VALIDATE_MEDIA_DESCRIPTOR_WRITER_FLAGS_HANDLE_GLOGS; + GstValidateMediaDescriptorWriter *writer = NULL; + GstValidateRunner *runner = NULL; + GstValidateMediaDescriptorParser *reference = NULL; + + GOptionEntry options[] = { + {"output-file", 'o', 0, G_OPTION_ARG_FILENAME, + &output_file, "The output file to store the results", + NULL}, + {"full", 'f', 0, G_OPTION_ARG_NONE, + &full, "Fully analyze the file frame by frame", + NULL}, + {"expected-results", 'e', 0, G_OPTION_ARG_FILENAME, + &expected_file, "Path to file containing the expected results " + "(or the last results found) for comparison with new results", + NULL}, + {"skip-parsers", 's', 0, G_OPTION_ARG_NONE, + &skip_parsers, "Do not plug a parser after demuxer.", + NULL}, + {NULL} + }; + + setlocale (LC_ALL, ""); + g_set_prgname ("gst-validate-media-check-" GST_API_VERSION); + ctx = g_option_context_new ("[URI]"); + g_option_context_set_summary (ctx, "Analyzes a media file and writes " + "the results to stdout or a file. Can also compare the results found " + "with another results file for identifying regressions. The monitoring" + " lib from gst-validate will be enabled during the tests to identify " + "issues with the gstreamer elements involved with the media file's " + "container and codec types"); + g_option_context_add_main_entries (ctx, options, NULL); + + if (!g_option_context_parse (ctx, &argc, &argv, &err)) { + g_printerr ("Error initializing: %s\n", err->message); + g_option_context_free (ctx); + g_clear_error (&err); + exit (1); + } + + gst_init (&argc, &argv); + gst_validate_init (); + + if (argc != 2) { + gchar *msg = g_option_context_get_help (ctx, TRUE, NULL); + g_printerr ("%s\n", msg); + g_free (msg); + g_option_context_free (ctx); + ret = 1; + goto out; + } + g_option_context_free (ctx); + + gst_validate_spin_on_fault_signals (); + + runner = gst_validate_runner_new (); + + if (expected_file) { + reference = + gst_validate_media_descriptor_parser_new (runner, expected_file, NULL); + + if (reference == NULL) { + gst_validate_printf (NULL, "Could not parse file: %s\n", expected_file); + ret = 1; + goto out; + } + + if (!full + && + gst_validate_media_descriptor_has_frame_info ( + (GstValidateMediaDescriptor *) + reference)) + full = TRUE; /* Reference has frame info, activate to do comparison */ + } + + if (full) + writer_flags |= GST_VALIDATE_MEDIA_DESCRIPTOR_WRITER_FLAGS_FULL; + + if (skip_parsers) + writer_flags |= GST_VALIDATE_MEDIA_DESCRIPTOR_WRITER_FLAGS_NO_PARSER; + + + writer = + gst_validate_media_descriptor_writer_new_discover (runner, argv[1], + writer_flags, NULL); + if (writer == NULL) { + gst_validate_printf (NULL, "Could not discover file: %s\n", argv[1]); + ret = 1; + goto out; + } + + if (output_file) { + if (!gst_validate_media_descriptor_writer_write (writer, output_file)) { + ret = 1; + goto out; + } + } + + if (reference) { + if (!gst_validate_media_descriptors_compare (GST_VALIDATE_MEDIA_DESCRIPTOR + (reference), GST_VALIDATE_MEDIA_DESCRIPTOR (writer))) { + ret = 1; + goto out; + } + } else { + output = gst_validate_media_descriptor_writer_serialize (writer); + gst_validate_printf (NULL, "Media info:\n%s\n", output); + g_free (output); + } + +out: + if (runner) + ret += gst_validate_runner_exit (runner, TRUE); + + g_free (output_file); + g_free (expected_file); + + if (reference) { + gst_validate_reporter_purge_reports (GST_VALIDATE_REPORTER (reference)); + gst_object_unref (reference); + } + if (writer) { + gst_validate_reporter_purge_reports (GST_VALIDATE_REPORTER (writer)); + gst_object_unref (writer); + } + if (runner) + gst_object_unref (runner); + gst_validate_deinit (); + gst_deinit (); + + gst_validate_printf (NULL, "\n=======> Test %s (Return value: %i)\n\n", + ret == 0 ? "PASSED" : "FAILED", ret); + + return ret; +} diff --git a/validate/tools/gst-validate-rtsp-server.c b/validate/tools/gst-validate-rtsp-server.c new file mode 100644 index 0000000000..017b2ad1b1 --- /dev/null +++ b/validate/tools/gst-validate-rtsp-server.c @@ -0,0 +1,159 @@ +/* GStreamer + * Copyright (C) 2008 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/* Cc'd from the test-uri example */ +#include + +#include +#include +#include + +#define DEFAULT_RTSP_PORT "8554" + +static char *port = (char *) DEFAULT_RTSP_PORT; + +static GOptionEntry entries[] = { + {"port", 'p', 0, G_OPTION_ARG_STRING, &port, + "Port to listen on (default: " DEFAULT_RTSP_PORT ")", "PORT"}, + {NULL} +}; + + +static gboolean +timeout (GstRTSPServer * server) +{ + GstRTSPSessionPool *pool; + + pool = gst_rtsp_server_get_session_pool (server); + gst_rtsp_session_pool_cleanup (pool); + g_object_unref (pool); + + return TRUE; +} + +#if 0 +static gboolean +remove_map (GstRTSPServer * server) +{ + GstRTSPMountPoints *mounts; + + gst_validate_printf (NULL, "removing /test mount point\n"); + mounts = gst_rtsp_server_get_mount_points (server); + gst_rtsp_mount_points_remove_factory (mounts, "/test"); + g_object_unref (mounts); + + return FALSE; +} +#endif + +int +main (int argc, gchar * argv[]) +{ + GMainLoop *loop; + GstRTSPServer *server; + GstRTSPMountPoints *mounts; + GstRTSPMediaFactoryURI *factory; + GOptionContext *optctx; + GError *error = NULL; + gchar *uri; + + optctx = g_option_context_new (" - GstValidate RTSP server, URI"); + g_option_context_add_main_entries (optctx, entries, NULL); + g_option_context_add_group (optctx, gst_init_get_option_group ()); + if (!g_option_context_parse (optctx, &argc, &argv, &error)) { + g_printerr ("Error parsing options: %s\n", error->message); + g_option_context_free (optctx); + g_clear_error (&error); + return -1; + } + g_option_context_free (optctx); + + if (argc < 2) { + g_printerr ("Please pass an URI or file as argument!\n"); + return -1; + } + + loop = g_main_loop_new (NULL, FALSE); + + /* create a server instance */ + server = gst_rtsp_server_new (); + g_object_set (server, "service", port, NULL); + + /* get the mount points for this server, every server has a default object + * that be used to map uri mount points to media factories */ + mounts = gst_rtsp_server_get_mount_points (server); + + /* make a URI media factory for a test stream. */ + factory = gst_rtsp_media_factory_uri_new (); + + /* when using GStreamer as a client, one can use the gst payloader, which is + * more efficient when there is no payloader for the compressed format */ + /* g_object_set (factory, "use-gstpay", TRUE, NULL); */ + + /* check if URI is valid, otherwise convert filename to URI if it's a file */ + if (gst_uri_is_valid (argv[1])) { + uri = g_strdup (argv[1]); + } else if (g_file_test (argv[1], G_FILE_TEST_EXISTS)) { + uri = gst_filename_to_uri (argv[1], NULL); + } else { + g_printerr ("Unrecognised command line argument '%s'.\n" + "Please pass an URI or file as argument!\n", argv[1]); + return -1; + } + + gst_rtsp_media_factory_uri_set_uri (factory, uri); + g_free (uri); + + /* if you want multiple clients to see the same video, set the shared property + * to TRUE */ + /* gst_rtsp_media_factory_set_shared ( GST_RTSP_MEDIA_FACTORY (factory), TRUE); */ + + /* attach the test factory to the /test url */ + gst_rtsp_mount_points_add_factory (mounts, "/test", + GST_RTSP_MEDIA_FACTORY (factory)); + + /* don't need the ref to the mapper anymore */ + g_object_unref (mounts); + + /* attach the server to the default maincontext */ + if (gst_rtsp_server_attach (server, NULL) == 0) + goto failed; + + /* do session cleanup every 2 seconds */ + g_timeout_add_seconds (2, (GSourceFunc) timeout, server); + +#if 0 + /* remove the mount point after 10 seconds, new clients won't be able to use + * the /test url anymore */ + g_timeout_add_seconds (10, (GSourceFunc) remove_map, server); +#endif + + /* start serving */ + g_print ("stream ready at rtsp://127.0.0.1:%s/test\n", port); + g_main_loop_run (loop); + + return 0; + + /* ERRORS */ +failed: + { + g_print ("failed to attach the server\n"); + return -1; + } +} diff --git a/validate/tools/gst-validate-transcoding.c b/validate/tools/gst-validate-transcoding.c new file mode 100644 index 0000000000..ce33a3ca04 --- /dev/null +++ b/validate/tools/gst-validate-transcoding.c @@ -0,0 +1,389 @@ +/* GStreamer + * + * Copyright (C) 2013 Collabora Ltd. + * Author: Thibault Saunier + * + * gst-validate-transcoding.c - CLI tool to validate transcoding operations + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include /* for LC_ALL */ + +#include +#include +#include +#include +#include +#include + + +#ifdef G_OS_UNIX +#include +#endif + +#include +#include + +static gint ret = 0; +static GstValidateMonitor *monitor = NULL; +static GstValidateRunner *runner = NULL; +static GstTranscoder *transcoder = NULL; +static gboolean eos_on_shutdown = FALSE; + +static gint +finish_transcoding (GstElement * pipeline, gint ret) +{ + int rep_err; + + if (!runner) { + ret = 1; + goto done; + } + + rep_err = gst_validate_runner_exit (runner, TRUE); + if (ret == 0) + ret = rep_err; + + gst_clear_object (&transcoder); + gst_clear_object (&pipeline); + gst_validate_reporter_purge_reports (GST_VALIDATE_REPORTER (monitor)); + g_object_unref (monitor); + g_object_unref (runner); + + gst_validate_deinit (); + gst_deinit (); + +done: + g_print ("\n=======> Test %s (Return value: %i)\n\n", + ret == 0 ? "PASSED" : "FAILED", ret); + + exit (ret); + return ret; +} + +#ifdef G_OS_UNIX +static gboolean +intr_handler (GstElement * pipeline) +{ + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, "gst-validate.interrupted"); + + if (eos_on_shutdown) { + eos_on_shutdown = FALSE; + gst_element_send_event (pipeline, gst_event_new_eos ()); + return TRUE; + + } + + finish_transcoding (pipeline, 1); + /* remove signal handler */ + return FALSE; +} +#endif /* G_OS_UNIX */ + +static gboolean +_execute_set_restriction (GstValidateScenario * scenario, + GstValidateAction * action) +{ + GstCaps *caps; + GType profile_type = G_TYPE_NONE; + const gchar *restriction_caps, *profile_type_name, *profile_name; + GstElement *pipeline = gst_validate_scenario_get_pipeline (scenario); + GstEncodingProfile *encoding_profile; + + g_object_get (pipeline, "profile", &encoding_profile, NULL); + restriction_caps = + gst_structure_get_string (action->structure, "restriction-caps"); + profile_type_name = + gst_structure_get_string (action->structure, "profile-type"); + profile_name = gst_structure_get_string (action->structure, "profile-name"); + + if (profile_type_name) { + profile_type = g_type_from_name (profile_type_name); + + if (profile_type == G_TYPE_NONE) { + gst_validate_abort ("Profile name %s not known", profile_name); + + return FALSE; + } else if (profile_type == GST_TYPE_ENCODING_CONTAINER_PROFILE) { + gst_validate_abort ("Can not set restrictions on container profiles"); + + return FALSE; + } + } else if (profile_name == NULL) { + if (g_strrstr (restriction_caps, "audio/x-raw") == restriction_caps) + profile_type = GST_TYPE_ENCODING_AUDIO_PROFILE; + else if (g_strrstr (restriction_caps, "video/x-raw") == restriction_caps) + profile_type = GST_TYPE_ENCODING_VIDEO_PROFILE; + else { + g_error + ("No information on what profiles to apply action, you should set either " + "profile_name or profile_type_name and the caps %s give us no hint", + restriction_caps); + + return FALSE; + } + } + + caps = gst_caps_from_string (restriction_caps); + if (caps == NULL) { + gst_validate_abort ("Could not parse caps: %s", restriction_caps); + + return FALSE; + } + + if (GST_IS_ENCODING_CONTAINER_PROFILE (encoding_profile)) { + gboolean found = FALSE; + const GList *tmp; + + for (tmp = + gst_encoding_container_profile_get_profiles + (GST_ENCODING_CONTAINER_PROFILE (encoding_profile)); tmp; + tmp = tmp->next) { + GstEncodingProfile *profile = tmp->data; + + if (profile_type != G_TYPE_NONE + && G_OBJECT_TYPE (profile) == profile_type) { + gst_encoding_profile_set_restriction (profile, gst_caps_copy (caps)); + found = TRUE; + } else if (profile_name + && g_strcmp0 (gst_encoding_profile_get_name (profile), + profile_name) == 0) { + gst_encoding_profile_set_restriction (profile, gst_caps_copy (caps)); + found = TRUE; + } + } + + if (!found) { + gst_validate_abort ("Could not find profile for %s%s", + profile_type_name ? profile_type_name : "", + profile_name ? profile_name : ""); + + gst_caps_unref (caps); + return FALSE; + + } + } + + if (profile_type != G_TYPE_NONE) { + gst_validate_printf (action, + "setting caps to %s on profiles of type %s\n", + restriction_caps, g_type_name (profile_type)); + } else { + gst_validate_printf (action, "setting caps to %s on profile %s\n", + restriction_caps, profile_name); + + } + + gst_caps_unref (caps); + return TRUE; +} + +static void +_register_actions (void) +{ +/* *INDENT-OFF* */ + gst_validate_register_action_type ("set-restriction", "validate-transcoding", _execute_set_restriction, + (GstValidateActionParameter []) { + { + .name = "restriction-caps", + .description = "The restriction caps to set on the encodebin " + "encoding profile.\nSee gst_encoding_profile_set_restriction()", + .mandatory = TRUE, + .types = "GstCaps serialized as a string" + }, + {NULL} + }, + "Change the restriction caps on the fly", + FALSE); + +/* *INDENT-ON* */ +} + +int +main (int argc, gchar ** argv) +{ + guint i; + GOptionContext *ctx; + gchar *output_file = NULL; + + const gchar *profile_str; + + GError *err = NULL; + gchar *scenario = NULL, *configs = NULL; + gboolean want_help = FALSE; + gboolean list_scenarios = FALSE, inspect_action_type = FALSE; + GstElement *pipeline = NULL; + gboolean force_reencoding = TRUE; + + GOptionEntry options[] = { + {"output-format", 'o', 0, G_OPTION_ARG_STRING, &profile_str, + "Set the properties to use for the encoding profile " + "(in case of transcoding.) For example:\n" + "video/mpegts:video/x-raw-yuv,width=1920,height=1080->video/x-h264:audio/x-ac3\n" + "A preset name can be used by adding +presetname, eg:\n" + "video/webm:video/x-vp8+mypreset:audio/x-vorbis\n" + "The presence property of the profile can be specified with |, eg:\n" + "video/webm:video/x-vp8|:audio/x-vorbis\n", + "properties-values"}, + {"set-scenario", '\0', 0, G_OPTION_ARG_FILENAME, &scenario, + "Let you set a scenario, it can be a full path to a scenario file" + " or the name of the scenario (name of the file without the" + " '.scenario' extension).", NULL}, + {"set-configs", '\0', 0, G_OPTION_ARG_STRING, &configs, + "Select a config scenario (one including 'is-config=true' in its" + " description). Specify multiple ones using ':' as separator." + " This option overrides the GST_VALIDATE_SCENARIO environment variable.", + NULL}, + {"list-scenarios", 'l', 0, G_OPTION_ARG_NONE, &list_scenarios, + "List the available scenarios that can be run", NULL}, + {"inspect-action-type", 't', 0, G_OPTION_ARG_NONE, &inspect_action_type, + "Inspect the available action types with which to write scenarios" + " if no parameter passed, it will list all available action types" + " otherwise will print the full description of the wanted types", + NULL}, + {"scenarios-defs-output-file", '\0', 0, G_OPTION_ARG_FILENAME, + &output_file, "The output file to store scenarios details. " + "Implies --list-scenarios", + NULL}, + {"force-reencoding", 'r', 0, G_OPTION_ARG_NONE, &force_reencoding, + "Whether to try to force reencoding, meaning trying to only remux " + "if possible(default: TRUE)", NULL}, + {"eos-on-shutdown", 'e', 0, G_OPTION_ARG_NONE, &eos_on_shutdown, + "If an EOS event should be sent to the pipeline if an interrupt is " + "received, instead of forcing the pipeline to stop. Sending an EOS " + "will allow the transcoding to finish the files properly before " + "exiting.", NULL}, + {NULL} + }; + + setlocale (LC_ALL, ""); + /* There is a bug that make gst_init remove the help param when initializing, + * it is FIXED in 1.0 */ + for (i = 1; i < argc; i++) { + if (!g_strcmp0 (argv[i], "--help") || !g_strcmp0 (argv[i], "-h")) + want_help = TRUE; + } + + if (!want_help) + gst_init (&argc, &argv); + + g_set_prgname ("gst-validate-transcoding-" GST_API_VERSION); + ctx = g_option_context_new ("[input-uri] [output-uri]"); + g_option_context_set_summary (ctx, "Transcodes input-uri to output-uri, " + "using the given encoding profile. The pipeline will be monitored for " + "possible issues detection using the gst-validate lib." + "\nCan also perform file conformance " + "tests after transcoding to make sure the result is correct"); + g_option_context_add_main_entries (ctx, options, NULL); + if (want_help) { + g_option_context_add_group (ctx, gst_init_get_option_group ()); + } + + if (!g_option_context_parse (ctx, &argc, &argv, &err)) { + g_printerr ("Error initializing: %s\n", err->message); + g_option_context_free (ctx); + g_clear_error (&err); + exit (1); + } + + g_option_context_free (ctx); + + if (want_help) + exit (0); + + if (scenario || configs) { + gchar *scenarios; + + if (scenario) + scenarios = g_strjoin (":", scenario, configs, NULL); + else + scenarios = g_strdup (configs); + + g_setenv ("GST_VALIDATE_SCENARIO", scenarios, TRUE); + g_free (scenarios); + } + + gst_validate_init (); + + if (list_scenarios || output_file) { + if (gst_validate_list_scenarios (argv + 1, argc - 1, output_file)) + return 1; + return 0; + } + + _register_actions (); + + if (inspect_action_type) { + if (gst_validate_print_action_types ((const gchar **) argv + 1, argc - 1)) + return 0; + + return -1; + } + + if (argc != 3) { + g_printerr ("%i arguments received, 2 expected.\n" + "You should run the test using:\n" + " ./gst-validate-transcoding-1.0 [options]\n", + argc - 1); + return 1; + } + + if (profile_str == NULL) { + GST_INFO ("Creating default encoding profile"); + + profile_str = "application/ogg:video/x-theora:audio/x-vorbis"; + } + + transcoder = gst_transcoder_new (argv[1], argv[2], profile_str); + gst_transcoder_set_avoid_reencoding (transcoder, !force_reencoding); + + /* Create the pipeline */ + runner = gst_validate_runner_new (); + + pipeline = gst_transcoder_get_pipeline (transcoder); +#ifdef G_OS_UNIX + g_unix_signal_add (SIGINT, (GSourceFunc) intr_handler, pipeline); +#endif + + gst_validate_spin_on_fault_signals (); + + monitor = + gst_validate_monitor_factory_create (GST_OBJECT_CAST (pipeline), runner, + NULL); + gst_validate_reporter_set_handle_g_logs (GST_VALIDATE_REPORTER (monitor)); + + if (!runner) { + gst_object_unref (pipeline); + gst_object_unref (transcoder); + g_printerr ("Failed to setup Validate Runner\n"); + exit (1); + } + + if (!gst_transcoder_run (transcoder, &err)) { + ret = -1; + GST_ERROR ("\nFAILURE: %s", err->message); + } + + return finish_transcoding (pipeline, ret); +} diff --git a/validate/tools/gst-validate.c b/validate/tools/gst-validate.c new file mode 100644 index 0000000000..8c5cbfa651 --- /dev/null +++ b/validate/tools/gst-validate.c @@ -0,0 +1,631 @@ +/* GStreamer + * + * Copyright (C) 2013 Collabora Ltd. + * Author: Thiago Sousa Santos + * + * gst-validate.c - Validate CLI launch line tool + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef G_OS_UNIX +#include +#endif +#include /* for LC_ALL */ + +static gint ret = 0; +static GMainLoop *mainloop; +static GstElement *pipeline; +static gboolean is_testfile; +static gboolean buffering = FALSE; + +static gboolean is_live = FALSE; + +#ifdef G_OS_UNIX +static gboolean +intr_handler (gpointer user_data) +{ + gst_validate_printf (NULL, "interrupt received.\n"); + + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, "gst-validate.interrupted"); + + g_main_loop_quit (mainloop); + + ret = SIGINT; + + /* Keep signal handler, it will be removed later anyway */ + return TRUE; +} +#endif /* G_OS_UNIX */ + +typedef struct +{ + GMainLoop *mainloop; + GstValidateMonitor *monitor; +} BusCallbackData; + +static gboolean +bus_callback (GstBus * bus, GstMessage * message, gpointer data) +{ + BusCallbackData *bus_callback_data = data; + GMainLoop *loop = bus_callback_data->mainloop; + GstValidateMonitor *monitor = bus_callback_data->monitor; + + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ERROR: + { + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, "gst-validate.error"); + + g_main_loop_quit (loop); + break; + } + case GST_MESSAGE_EOS: + if (!g_getenv ("GST_VALIDATE_SCENARIO") && !is_testfile) + g_main_loop_quit (loop); + break; + case GST_MESSAGE_ASYNC_DONE: + break; + case GST_MESSAGE_LATENCY: + gst_bin_recalculate_latency (GST_BIN (pipeline)); + break; + case GST_MESSAGE_STATE_CHANGED: + if (GST_MESSAGE_SRC (message) == GST_OBJECT (pipeline)) { + GstState oldstate, newstate, pending; + gchar *dump_name; + gchar *state_transition_name; + + gst_message_parse_state_changed (message, &oldstate, &newstate, + &pending); + + GST_DEBUG ("State changed (old: %s, new: %s, pending: %s)", + gst_element_state_get_name (oldstate), + gst_element_state_get_name (newstate), + gst_element_state_get_name (pending)); + + state_transition_name = g_strdup_printf ("%s_%s", + gst_element_state_get_name (oldstate), + gst_element_state_get_name (newstate)); + dump_name = g_strconcat ("gst-validate.", state_transition_name, NULL); + + + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, dump_name); + + g_free (dump_name); + g_free (state_transition_name); + } + + break; + case GST_MESSAGE_WARNING:{ + GError *gerror; + gchar *debug; + gchar *name = gst_object_get_path_string (GST_MESSAGE_SRC (message)); + + /* dump graph on warning */ + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, "gst-validate.warning"); + + gst_message_parse_warning (message, &gerror, &debug); + gst_validate_printf (NULL, "WARNING: from element %s: %s\n", name, + gerror->message); + if (debug) + gst_validate_printf (NULL, "Additional debug info:\n%s\n", debug); + + g_clear_error (&gerror); + g_free (debug); + g_free (name); + break; + } + case GST_MESSAGE_BUFFERING:{ + gint percent; + GstBufferingMode mode; + GstState target_state = GST_STATE_PLAYING; + gboolean monitor_handles_state; + + g_object_get (monitor, "handles-states", &monitor_handles_state, NULL); + if (monitor_handles_state && GST_IS_VALIDATE_BIN_MONITOR (monitor)) { + target_state = + gst_validate_scenario_get_target_state (GST_VALIDATE_BIN_MONITOR + (monitor)->scenario); + } + + if (!buffering) { + gst_validate_printf (NULL, "\n"); + } + + gst_message_parse_buffering (message, &percent); + gst_message_parse_buffering_stats (message, &mode, NULL, NULL, NULL); + + /* no state management needed for live pipelines */ + if (mode == GST_BUFFERING_LIVE) { + is_live = TRUE; + break; + } + + if (percent == 100) { + /* a 100% message means buffering is done */ + if (buffering) { + buffering = FALSE; + + if (target_state == GST_STATE_PLAYING) { + gst_element_set_state (pipeline, GST_STATE_PLAYING); + } + } + } else { + /* buffering... */ + if (!buffering) { + gst_element_set_state (pipeline, GST_STATE_PAUSED); + buffering = TRUE; + } + } + break; + } + case GST_MESSAGE_REQUEST_STATE: + { + GstState state; + + gst_message_parse_request_state (message, &state); + + if (GST_IS_VALIDATE_SCENARIO (GST_MESSAGE_SRC (message)) + && state == GST_STATE_NULL) { + gst_validate_printf (GST_MESSAGE_SRC (message), + "State change request NULL, quitting mainloop\n"); + g_main_loop_quit (mainloop); + } + break; + } + default: + break; + } + + return TRUE; +} + +static gboolean +_is_playbin_pipeline (int argc, gchar ** argv) +{ + gint i; + + for (i = 0; i < argc; i++) { + if (g_ascii_strncasecmp (argv[i], "playbin", 7) == 0) { + return TRUE; + } + } + + return FALSE; +} + +static gboolean +_execute_set_subtitles (GstValidateScenario * scenario, + GstValidateAction * action) +{ + gchar *uri, *fname; + GFile *tmpfile, *folder; + const gchar *subtitle_file, *subtitle_dir; + GstElement *pipeline = gst_validate_scenario_get_pipeline (scenario); + + if (pipeline == NULL) { + GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR, + "Can't execute a '%s' action after the pipeline " + "has been destroyed.", action->type); + return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + } + + subtitle_file = gst_structure_get_string (action->structure, "subtitle-file"); + if (subtitle_file == NULL) { + GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR, + "No 'subtitle-file' specified in 'set-subtile'"); + gst_object_unref (pipeline); + + return GST_VALIDATE_EXECUTE_ACTION_ERROR; + } + + subtitle_dir = gst_structure_get_string (action->structure, "subtitle-dir"); + g_object_get (pipeline, "current-uri", &uri, NULL); + tmpfile = g_file_new_for_uri (uri); + g_free (uri); + + folder = g_file_get_parent (tmpfile); + + fname = g_strdup_printf ("%s%s%s%s", + subtitle_dir ? subtitle_dir : "", + subtitle_dir ? G_DIR_SEPARATOR_S : "", + g_file_get_basename (tmpfile), subtitle_file); + gst_object_unref (tmpfile); + + tmpfile = g_file_get_child (folder, fname); + g_free (fname); + gst_object_unref (folder); + + uri = g_file_get_uri (tmpfile); + gst_validate_printf (action, "Setting subtitle file to: %s", uri); + g_object_set (pipeline, "suburi", uri, NULL); + g_free (uri); + gst_object_unref (pipeline); + + return TRUE; +} + +static void +_register_playbin_actions (void) +{ +/* *INDENT-OFF* */ + gst_validate_register_action_type ("set-subtitle", "validate-launcher", _execute_set_subtitles, + (GstValidateActionParameter []) { + { + .name = "subtitle-file", + .description = "Sets a subtitles file on a playbin pipeline", + .mandatory = TRUE, + .types = "string (A URI)", + NULL + }, + {NULL} + }, + "Action to set a subtitle file to use on a playbin pipeline.\n" + "The subtitles file that will be used should be specified\n" + "relative to the playbin URI in use thanks to the subtitle-file\n" + "action property. You can also specify a folder with subtitle-dir\n" + "For example if playbin.uri='file://some/uri.mov'\n" + "and action looks like 'set-subtitle, subtitle-file=en.srt'\n" + "the subtitle URI will be set to 'file:///some/uri.mov.en.srt'\n", + FALSE); +/* *INDENT-ON* */ +} + +int main (int argc, gchar ** argv); + +static int +run_test_from_file (gchar * testfile, gboolean use_fakesinks) +{ + gint argc, ret; + gchar **args, **argv; + GstStructure *meta = gst_validate_setup_test_file (testfile, use_fakesinks); + + args = gst_validate_utils_get_strv (meta, "args"); + if (!args) + gst_validate_abort ("No 'args' in .validatetest meta structure: %s", + gst_structure_to_string (meta)); + + for (argc = 0; args[argc]; argc++); + argc++; + + argv = g_new0 (char *, argc + 1); + argv[0] = (gchar *) "gst-validate-" GST_API_VERSION; + memcpy (&argv[1], args, sizeof (char *) * (argc)); + + ret = main (argc, argv); + + g_strfreev (args); + g_free (argv); + g_free (testfile); + return ret; +} + +int +main (int argc, gchar ** argv) +{ + GError *err = NULL; + gchar *scenario = NULL, *configs = NULL, *media_info = NULL, + *verbosity = NULL, *testfile = NULL; + gboolean list_scenarios = FALSE, monitor_handles_state, + inspect_action_type = FALSE, print_issue_types = FALSE; + GstStateChangeReturn sret; + gchar *output_file = NULL; + BusCallbackData bus_callback_data = { 0, }; + gboolean use_fakesinks = FALSE; + +#ifdef G_OS_UNIX + guint signal_watch_id; +#endif + int rep_err; + + GOptionEntry options[] = { + {"set-test-file", '\0', 0, G_OPTION_ARG_FILENAME, &testfile, + "Let you set a all container testfile", NULL}, + {"set-scenario", '\0', 0, G_OPTION_ARG_FILENAME, &scenario, + "Let you set a scenario, it can be a full path to a scenario file" + " or the name of the scenario (name of the file without the" + " '.scenario' extension).", NULL}, + {"list-scenarios", 'l', 0, G_OPTION_ARG_NONE, &list_scenarios, + "List the available scenarios that can be run", NULL}, + {"use-fakesinks", 'm', 0, G_OPTION_ARG_NONE, &use_fakesinks, + "Use fakesinks when possible. This will have effect when using" + " test files.", NULL}, + {"verbosity", 'v', 0, G_OPTION_ARG_STRING, &verbosity, + "Set overall verbosity as defined by GstValidateVerbosityFlags" + " as a string", NULL}, + {"scenarios-defs-output-file", '\0', 0, G_OPTION_ARG_FILENAME, + &output_file, "The output file to store scenarios details. " + "Implies --list-scenarios", + NULL}, + {"inspect-action-type", 't', 0, G_OPTION_ARG_NONE, &inspect_action_type, + "Inspect the available action types with which to write scenarios." + " Specify an action type if you want its full description." + " If no action type is given the full list of available ones gets printed." + "Note that passing \"all\" as action type name, makes it output the" + " full documentation for all types.", + NULL}, + {"print-issue-types", '\0', 0, G_OPTION_ARG_NONE, &print_issue_types, + "List all known issue types and their descriptions.", + NULL}, + {"set-media-info", '\0', 0, G_OPTION_ARG_FILENAME, &media_info, + "Set a media_info XML file descriptor to share information about the" + " media file that will be reproduced.", + NULL}, + {"set-configs", '\0', 0, G_OPTION_ARG_STRING, &configs, + "Select a config scenario (one including 'is-config=true' in its" + " description). Specify multiple ones using ':' as separator." + " This option overrides the GST_VALIDATE_SCENARIO environment variable.", + NULL}, + {NULL} + }; + GOptionContext *ctx; + gchar **argvn; + GstValidateRunner *runner; + GstValidateMonitor *monitor; + GstBus *bus; + + setlocale (LC_ALL, ""); + + g_set_prgname ("gst-validate-" GST_API_VERSION); + ctx = g_option_context_new ("PIPELINE-DESCRIPTION"); + g_option_context_add_main_entries (ctx, options, NULL); + g_option_context_set_summary (ctx, "Runs a gst launch pipeline, adding " + "monitors to it to identify issues in the used elements. At the end" + " a report will be printed. To view issues as they are created, set" + " the env var GST_DEBUG=validate:2 and it will be printed " + "as gstreamer debugging"); + + if (argc == 1) { + g_print ("%s", g_option_context_get_help (ctx, FALSE, NULL)); + exit (1); + } + + if (!g_option_context_parse (ctx, &argc, &argv, &err)) { + g_printerr ("Error initializing: %s\n", err->message); + g_option_context_free (ctx); + g_clear_error (&err); + exit (1); + } + + gst_init (&argc, &argv); + gst_validate_init_debug (); + if (testfile) { + is_testfile = TRUE; + if (scenario) + gst_validate_abort + ("Can not specify scenario and testfile at the same time"); + g_option_context_free (ctx); + return run_test_from_file (testfile, use_fakesinks); + } + + if (scenario || configs) { + gchar *scenarios; + + if (scenario) + scenarios = g_strjoin (":", scenario, configs, NULL); + else + scenarios = g_strdup (configs); + + g_setenv ("GST_VALIDATE_SCENARIO", scenarios, TRUE); + g_free (scenarios); + g_free (scenario); + g_free (configs); + } + + gst_validate_init (); + + if (list_scenarios || output_file) { + g_option_context_free (ctx); + if (gst_validate_list_scenarios (argv + 1, argc - 1, output_file)) + return 1; + return 0; + } + + if (inspect_action_type) { + _register_playbin_actions (); + + if (!gst_validate_print_action_types ((const gchar **) argv + 1, argc - 1)) { + GST_ERROR ("Could not print all wanted types"); + return -1; + } + + return 0; + } + + if (print_issue_types) { + gst_validate_print_issues (); + return 0; + } + + if (argc == 1) { + gst_validate_printf (NULL, "%s", g_option_context_get_help (ctx, FALSE, + NULL)); + g_option_context_free (ctx); + exit (1); + } + + g_option_context_free (ctx); + + runner = gst_validate_runner_new (); + if (!runner) { + g_printerr ("Failed to setup Validate Runner\n"); + exit (1); + } + + /* Create the pipeline */ + argvn = g_new0 (char *, argc); + memcpy (argvn, argv + 1, sizeof (char *) * (argc - 1)); + if (argc == 2) { + gst_validate_printf (NULL, "**-> Pipeline: '%s'**\n", argvn[0]); + pipeline = (GstElement *) gst_parse_launch (argvn[0], &err); + } else { + pipeline = (GstElement *) gst_parse_launchv ((const gchar **) argvn, &err); + } + + if (!pipeline) { + gst_validate_printf (NULL, "Failed to create pipeline: %s\n", + err ? err->message : "unknown reason"); + g_clear_error (&err); + g_object_unref (runner); + + g_free (argvn); + exit (1); + } else if (err) { + if (g_error_matches (err, GST_PARSE_ERROR, GST_PARSE_ERROR_NO_SUCH_ELEMENT)) { + if (!gst_validate_fail_on_missing_plugin ()) + gst_validate_skip_test ("missing plugin: %s", err->message); + } + g_printerr ("Erroneous pipeline: %s\n", + err->message ? err->message : "unknown reason"); + g_clear_error (&err); + g_free (argvn); + return 1; + } + + if (!GST_IS_PIPELINE (pipeline)) { + GstElement *new_pipeline = gst_pipeline_new (""); + + gst_bin_add (GST_BIN (new_pipeline), pipeline); + pipeline = new_pipeline; + } + + gst_pipeline_set_auto_flush_bus (GST_PIPELINE (pipeline), FALSE); +#ifdef G_OS_UNIX + signal_watch_id = + g_unix_signal_add (SIGINT, (GSourceFunc) intr_handler, pipeline); +#endif + + gst_validate_spin_on_fault_signals (); + + if (_is_playbin_pipeline (argc - 1, argv + 1)) { + _register_playbin_actions (); + } + + monitor = gst_validate_monitor_factory_create (GST_OBJECT_CAST (pipeline), + runner, NULL); + if (verbosity) + gst_util_set_object_arg (G_OBJECT (monitor), "verbosity", verbosity); + gst_validate_reporter_set_handle_g_logs (GST_VALIDATE_REPORTER (monitor)); + + if (media_info) { + GError *err = NULL; + GstValidateMediaDescriptorParser *parser = + gst_validate_media_descriptor_parser_new (runner, + media_info, &err); + + if (parser == NULL) { + GST_ERROR ("Could not use %s as a media-info file (error: %s)", + media_info, err ? err->message : "Unknown error"); + + g_free (media_info); + exit (1); + } + + gst_validate_monitor_set_media_descriptor (monitor, + GST_VALIDATE_MEDIA_DESCRIPTOR (parser)); + gst_object_unref (parser); + g_free (media_info); + } + + mainloop = g_main_loop_new (NULL, FALSE); + bus = gst_element_get_bus (pipeline); + gst_bus_add_signal_watch (bus); + bus_callback_data.mainloop = mainloop; + bus_callback_data.monitor = monitor; + g_signal_connect (bus, "message", (GCallback) bus_callback, + &bus_callback_data); + + gst_validate_printf (NULL, "**-> Starting pipeline**\n"); + g_free (argvn); + g_object_get (monitor, "handles-states", &monitor_handles_state, NULL); + if (monitor_handles_state == FALSE) { + sret = gst_element_set_state (pipeline, GST_STATE_PLAYING); + switch (sret) { + case GST_STATE_CHANGE_FAILURE: + /* ignore, we should get an error message posted on the bus */ + gst_validate_printf (NULL, "Pipeline failed to go to PLAYING state\n"); + gst_element_set_state (pipeline, GST_STATE_NULL); + ret = -1; + goto exit; + case GST_STATE_CHANGE_NO_PREROLL: + gst_validate_printf (NULL, "Pipeline is live.\n"); + is_live = TRUE; + break; + case GST_STATE_CHANGE_ASYNC: + gst_validate_printf (NULL, "Prerolling...\r"); + break; + default: + break; + } + gst_validate_printf (NULL, "**-> Pipeline started**\n"); + } else { + gst_validate_printf (NULL, "**-> Letting scenario handle set state**\n"); + } + + g_main_loop_run (mainloop); + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_element_get_state (pipeline, NULL, NULL, GST_CLOCK_TIME_NONE); + + /* Clean the bus */ + gst_bus_set_flushing (bus, TRUE); + gst_bus_remove_signal_watch (bus); + gst_object_unref (bus); + + rep_err = gst_validate_runner_exit (runner, TRUE); + if (ret == 0) { + ret = rep_err; + if (rep_err != 0) + gst_validate_printf (NULL, "Returning %d as errors were found\n", + rep_err); + } + +exit: + g_main_loop_unref (mainloop); + g_object_unref (pipeline); + g_object_unref (runner); + gst_validate_reporter_purge_reports (GST_VALIDATE_REPORTER (monitor)); + g_object_unref (monitor); + g_clear_error (&err); +#ifdef G_OS_UNIX + g_source_remove (signal_watch_id); +#endif + + gst_validate_printf (NULL, "\n=======> Test %s (Return value: %i)\n\n", + ret == 0 ? "PASSED" : "FAILED", ret); + + gst_validate_deinit (); + gst_deinit (); + return ret; +} diff --git a/validate/tools/meson.build b/validate/tools/meson.build new file mode 100644 index 0000000000..b647793431 --- /dev/null +++ b/validate/tools/meson.build @@ -0,0 +1,67 @@ +executable('gst-validate-' + apiversion, + 'gst-validate.c', + install: true, + include_directories : inc_dirs, + dependencies : validate_dep, + c_args : [gst_c_args], + ) + +gst_transcoder_dep = dependency('gstreamer-transcoder-' + apiversion, version : gst_req, + fallback : ['gst-plugins-bad', 'gst_transcoder_dep'], required: false) + +if gst_transcoder_dep.found() + executable('gst-validate-transcoding-' + apiversion, + 'gst-validate-transcoding.c', + install: true, + include_directories : inc_dirs, + dependencies : [validate_dep, gst_transcoder_dep], + c_args : [gst_c_args], + ) +else + warning('Can not build gst-validate-transcoding-' + apiversion) +endif + +executable('gst-validate-media-check-' + apiversion, + 'gst-validate-media-check.c', + install: true, + include_directories : inc_dirs, + dependencies : validate_dep, + c_args : [gst_c_args], + ) + +rtsp_server_dep = dependency('gstreamer-rtsp-server-' + apiversion, + fallback: ['gst-rtsp-server', 'gst_rtsp_server_dep'], + version : gst_req, + required: false) + +if rtsp_server_dep.found() + executable('gst-validate-rtsp-server-' + apiversion, + 'gst-validate-rtsp-server.c', + install: true, + include_directories: inc_dirs, + dependencies : [rtsp_server_dep, validate_dep], + c_args: [gst_c_args], + ) +endif + +if validate_video_dep.found() + executable('gst-validate-images-check-' + apiversion, + 'gst-validate-images-check.c', + install: true, + include_directories : inc_dirs, + dependencies : [validate_dep, validate_video_dep], + c_args : [gst_c_args] + ) +endif + +tmpconf = configuration_data() +tmpconf.set('LIBDIR', join_paths(get_option('prefix'), get_option('libdir'))) +tmpconf.set('BUILDDIR', meson.current_build_dir()) +tmpconf.set('SRCDIR', meson.current_source_dir()) + +configure_file(input : 'gst-validate-launcher.in', + install_dir: get_option('bindir'), + output : 'gst-validate-launcher', + configuration : tmpconf) + +launcher = find_program(meson.current_build_dir() + '/gst-validate-launcher')