diff options
Diffstat (limited to 'test')
92 files changed, 1658 insertions, 971 deletions
diff --git a/test/bayer-format.cpp b/test/bayer-format.cpp index 54f03487..f8d19804 100644 --- a/test/bayer-format.cpp +++ b/test/bayer-format.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Sebastian Fricke * - * bayer_format.cpp - BayerFormat class tests + * BayerFormat class tests */ #include <iostream> diff --git a/test/byte-stream-buffer.cpp b/test/byte-stream-buffer.cpp index 04ff0571..04aab3d2 100644 --- a/test/byte-stream-buffer.cpp +++ b/test/byte-stream-buffer.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2018, Google Inc. * - * byte_stream_buffer.cpp - ByteStreamBuffer tests + * ByteStreamBuffer tests */ #include <array> diff --git a/test/camera-sensor.cpp b/test/camera-sensor.cpp index d3dcb510..1d402c43 100644 --- a/test/camera-sensor.cpp +++ b/test/camera-sensor.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * camera-sensor.cpp - Camera sensor tests + * Camera sensor tests */ #include <algorithm> @@ -12,6 +12,7 @@ #include <libcamera/base/utils.h> +#include "libcamera/internal/camera_lens.h" #include "libcamera/internal/camera_sensor.h" #include "libcamera/internal/device_enumerator.h" #include "libcamera/internal/media_device.h" @@ -57,6 +58,10 @@ protected: return TestFail; } + lens_ = sensor_->focusLens(); + if (lens_) + cout << "Found lens controller" << endl; + return TestPass; } @@ -95,7 +100,7 @@ protected: MEDIA_BUS_FMT_SBGGR10_1X10, MEDIA_BUS_FMT_BGR888_1X24 }, Size(1024, 768)); - if (format.mbus_code != MEDIA_BUS_FMT_SBGGR10_1X10 || + if (format.code != MEDIA_BUS_FMT_SBGGR10_1X10 || format.size != Size(4096, 2160)) { cerr << "Failed to get a suitable format, expected 4096x2160-0x" << utils::hex(MEDIA_BUS_FMT_SBGGR10_1X10) @@ -103,6 +108,11 @@ protected: return TestFail; } + if (lens_ && lens_->setFocusPosition(10)) { + cerr << "Failed to set lens focus position" << endl; + return TestFail; + } + return TestPass; } @@ -115,6 +125,7 @@ private: std::unique_ptr<DeviceEnumerator> enumerator_; std::shared_ptr<MediaDevice> media_; CameraSensor *sensor_; + CameraLens *lens_; }; TEST_REGISTER(CameraSensorTest) diff --git a/test/camera/camera_reconfigure.cpp b/test/camera/camera_reconfigure.cpp index f6076baa..06c87730 100644 --- a/test/camera/camera_reconfigure.cpp +++ b/test/camera/camera_reconfigure.cpp @@ -98,7 +98,7 @@ private: return TestFail; } - requests_.push_back(move(request)); + requests_.push_back(std::move(request)); } camera_->requestCompleted.connect(this, &CameraReconfigure::requestComplete); @@ -179,7 +179,7 @@ private: continue; string pname("/proc/" + string(ptr->d_name) + "/comm"); - if (File::exists(pname.c_str())) { + if (File::exists(pname)) { ifstream pfile(pname.c_str()); string comm; getline(pfile, comm); diff --git a/test/camera/meson.build b/test/camera/meson.build index 668d5c03..4f9f8c8c 100644 --- a/test/camera/meson.build +++ b/test/camera/meson.build @@ -3,18 +3,18 @@ # Tests are listed in order of complexity. # They are not alphabetically sorted. camera_tests = [ - ['configuration_default', 'configuration_default.cpp'], - ['configuration_set', 'configuration_set.cpp'], - ['buffer_import', 'buffer_import.cpp'], - ['statemachine', 'statemachine.cpp'], - ['capture', 'capture.cpp'], - ['camera_reconfigure', 'camera_reconfigure.cpp'], + {'name': 'configuration_default', 'sources': ['configuration_default.cpp']}, + {'name': 'configuration_set', 'sources': ['configuration_set.cpp']}, + {'name': 'buffer_import', 'sources': ['buffer_import.cpp']}, + {'name': 'statemachine', 'sources': ['statemachine.cpp']}, + {'name': 'capture', 'sources': ['capture.cpp']}, + {'name': 'camera_reconfigure', 'sources': ['camera_reconfigure.cpp']}, ] -foreach t : camera_tests - exe = executable(t[0], t[1], +foreach test : camera_tests + exe = executable(test['name'], test['sources'], dependencies : libcamera_private, link_with : test_libraries, include_directories : test_includes_internal) - test(t[0], exe, suite : 'camera', is_parallel : false) + test(test['name'], exe, suite : 'camera', is_parallel : false) endforeach diff --git a/test/color-space.cpp b/test/color-space.cpp new file mode 100644 index 00000000..7d45b217 --- /dev/null +++ b/test/color-space.cpp @@ -0,0 +1,105 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2022, Laurent Pinchart <laurent.pinchart@ideasonboard.com> + * + * libcamera ColorSpace test + */ + +#include <array> +#include <iostream> + +#include <libcamera/color_space.h> + +#include "test.h" + +using namespace libcamera; +using namespace std; + +class ColorSpaceTest : public Test +{ +protected: + int run() + { + if (ColorSpace::toString(std::nullopt) != "Unset") { + std::cerr << "Conversion from nullopt to string failed" << std::endl; + return TestFail; + } + + const std::array<std::pair<ColorSpace, std::string>, 10> colorSpaces = { { + { ColorSpace::Raw, "RAW" }, + { ColorSpace::Srgb, "sRGB" }, + { ColorSpace::Sycc, "sYCC" }, + { ColorSpace::Smpte170m, "SMPTE170M" }, + { ColorSpace::Rec709, "Rec709" }, + { ColorSpace::Rec2020, "Rec2020" }, + { + ColorSpace{ + ColorSpace::Primaries::Raw, + ColorSpace::TransferFunction::Linear, + ColorSpace::YcbcrEncoding::None, + ColorSpace::Range::Limited + }, + "RAW/Linear/None/Limited" + }, { + ColorSpace{ + ColorSpace::Primaries::Smpte170m, + ColorSpace::TransferFunction::Srgb, + ColorSpace::YcbcrEncoding::Rec601, + ColorSpace::Range::Full + }, + "SMPTE170M/sRGB/Rec601/Full" + }, { + ColorSpace{ + ColorSpace::Primaries::Rec709, + ColorSpace::TransferFunction::Rec709, + ColorSpace::YcbcrEncoding::Rec709, + ColorSpace::Range::Full + }, + "Rec709/Rec709/Rec709/Full" + }, { + ColorSpace{ + ColorSpace::Primaries::Rec2020, + ColorSpace::TransferFunction::Linear, + ColorSpace::YcbcrEncoding::Rec2020, + ColorSpace::Range::Limited + }, + "Rec2020/Linear/Rec2020/Limited" + }, + } }; + + for (const auto &[colorSpace, name] : colorSpaces) { + if (colorSpace.toString() != name) { + std::cerr + << "Conversion from ColorSpace to string failed: " + << "expected " << name + << ", got " << colorSpace.toString() + << std::endl; + return TestFail; + } + + if (ColorSpace::fromString(name) != colorSpace) { + std::cerr + << "Conversion from string " + << name << " to ColorSpace failed" + << std::endl; + return TestFail; + } + } + + if (ColorSpace::fromString("Invalid")) { + std::cerr << "Conversion from invalid name string to color space succeeded" + << std::endl; + return TestFail; + } + + if (ColorSpace::fromString("Rec709/Rec709/Rec710/Limited")) { + std::cerr << "Conversion from invalid component string to color space succeeded" + << std::endl; + return TestFail; + } + + return TestPass; + } +}; + +TEST_REGISTER(ColorSpaceTest) diff --git a/test/controls/control_info.cpp b/test/controls/control_info.cpp index 2827473b..e1bb43f0 100644 --- a/test/controls/control_info.cpp +++ b/test/controls/control_info.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * control_info.cpp - ControlInfo tests + * ControlInfo tests */ #include <iostream> @@ -26,20 +26,22 @@ protected: */ ControlInfo brightness; - if (brightness.min().get<int32_t>() != 0 || - brightness.max().get<int32_t>() != 0) { + if (brightness.min().type() != ControlType::ControlTypeNone || + brightness.max().type() != ControlType::ControlTypeNone || + brightness.def().type() != ControlType::ControlTypeNone) { cout << "Invalid control range for Brightness" << endl; return TestFail; } /* * Test information retrieval from a control with a minimum and - * a maximum value. + * a maximum value, and an implicit default value. */ ControlInfo contrast(10, 200); if (contrast.min().get<int32_t>() != 10 || - contrast.max().get<int32_t>() != 200) { + contrast.max().get<int32_t>() != 200 || + !contrast.def().isNone()) { cout << "Invalid control range for Contrast" << endl; return TestFail; } diff --git a/test/controls/control_info_map.cpp b/test/controls/control_info_map.cpp index db95945a..b0be14b5 100644 --- a/test/controls/control_info_map.cpp +++ b/test/controls/control_info_map.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * control_info.cpp - ControlInfoMap tests + * ControlInfoMap tests */ #include <iostream> @@ -75,6 +75,13 @@ protected: return TestFail; } + /* Test looking up a control on a default-constructed infoMap */ + const ControlInfoMap emptyInfoMap; + if (emptyInfoMap.find(12345) != emptyInfoMap.end()) { + cerr << "find() on empty ControlInfoMap failed" << endl; + return TestFail; + } + return TestPass; } }; diff --git a/test/controls/control_list.cpp b/test/controls/control_list.cpp index 70cf61b8..e27325c3 100644 --- a/test/controls/control_list.cpp +++ b/test/controls/control_list.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * control_list.cpp - ControlList tests + * ControlList tests */ #include <iostream> @@ -50,7 +50,7 @@ protected: return TestFail; } - if (list.contains(controls::Brightness)) { + if (list.get(controls::Brightness)) { cout << "List should not contain Brightness control" << endl; return TestFail; } @@ -80,7 +80,7 @@ protected: return TestFail; } - if (!list.contains(controls::Brightness)) { + if (!list.get(controls::Brightness)) { cout << "List should contain Brightness control" << endl; return TestFail; } @@ -99,7 +99,7 @@ protected: return TestFail; } - if (list.contains(controls::Contrast)) { + if (list.get(controls::Contrast)) { cout << "List should not contain Contract control" << endl; return TestFail; } @@ -108,8 +108,8 @@ protected: list.set(controls::Brightness, 0.0f); list.set(controls::Contrast, 1.5f); - if (!list.contains(controls::Brightness) || - !list.contains(controls::Contrast)) { + if (!list.get(controls::Brightness) || + !list.get(controls::Contrast)) { cout << "List should contain Brightness and Contrast controls" << endl; return TestFail; @@ -145,7 +145,7 @@ protected: */ list.set(controls::AwbEnable, true); - if (list.contains(controls::AwbEnable)) { + if (list.get(controls::AwbEnable)) { cout << "List shouldn't contain AwbEnable control" << endl; return TestFail; } @@ -171,9 +171,9 @@ protected: return TestFail; } - if (!mergeList.contains(controls::Brightness) || - !mergeList.contains(controls::Contrast) || - !mergeList.contains(controls::Saturation)) { + if (!mergeList.get(controls::Brightness) || + !mergeList.get(controls::Contrast) || + !mergeList.get(controls::Saturation)) { cout << "Merged list does not contain all controls" << endl; return TestFail; } @@ -196,6 +196,56 @@ protected: return TestFail; } + /* + * Create two lists with overlapping controls. Merge them with + * overwriteExisting = true, verifying that the existing control + * values *get* overwritten. + */ + mergeList.clear(); + mergeList.set(controls::Brightness, 0.7f); + mergeList.set(controls::Saturation, 0.4f); + + list.clear(); + list.set(controls::Brightness, 0.5f); + list.set(controls::Contrast, 1.1f); + + mergeList.merge(list, ControlList::MergePolicy::OverwriteExisting); + if (mergeList.size() != 3) { + cout << "Merged list should contain three elements" << endl; + return TestFail; + } + + if (list.size() != 2) { + cout << "The list to merge should contain two elements" + << endl; + return TestFail; + } + + if (!mergeList.get(controls::Brightness) || + !mergeList.get(controls::Contrast) || + !mergeList.get(controls::Saturation)) { + cout << "Merged list does not contain all controls" << endl; + return TestFail; + } + + if (mergeList.get(controls::Brightness) != 0.5f) { + cout << "Brightness control value did not change after merging lists" + << endl; + return TestFail; + } + + if (mergeList.get(controls::Contrast) != 1.1f) { + cout << "Contrast control value changed after merging lists" + << endl; + return TestFail; + } + + if (mergeList.get(controls::Saturation) != 0.4f) { + cout << "Saturation control value changed after merging lists" + << endl; + return TestFail; + } + return TestPass; } }; diff --git a/test/controls/control_value.cpp b/test/controls/control_value.cpp index ad8e05d0..344107fa 100644 --- a/test/controls/control_value.cpp +++ b/test/controls/control_value.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * control_value.cpp - ControlValue tests + * ControlValue tests */ #include <algorithm> diff --git a/test/controls/meson.build b/test/controls/meson.build index 0103543e..763f8905 100644 --- a/test/controls/meson.build +++ b/test/controls/meson.build @@ -1,16 +1,16 @@ # SPDX-License-Identifier: CC0-1.0 control_tests = [ - ['control_info', 'control_info.cpp'], - ['control_info_map', 'control_info_map.cpp'], - ['control_list', 'control_list.cpp'], - ['control_value', 'control_value.cpp'], + {'name': 'control_info', 'sources': ['control_info.cpp']}, + {'name': 'control_info_map', 'sources': ['control_info_map.cpp']}, + {'name': 'control_list', 'sources': ['control_list.cpp']}, + {'name': 'control_value', 'sources': ['control_value.cpp']}, ] -foreach t : control_tests - exe = executable(t[0], t[1], +foreach test : control_tests + exe = executable(test['name'], test['sources'], dependencies : libcamera_public, link_with : test_libraries, include_directories : test_includes_internal) - test(t[0], exe, suite : 'controls', is_parallel : false) + test(test['name'], exe, suite : 'controls', is_parallel : false) endforeach diff --git a/test/delayed_controls.cpp b/test/delayed_controls.cpp index c6f195b7..7bd30e7a 100644 --- a/test/delayed_controls.cpp +++ b/test/delayed_controls.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * delayed_controls.cpp - libcamera delayed controls test + * libcamera delayed controls test */ #include <iostream> @@ -155,7 +155,7 @@ protected: return TestPass; } - int dualControlsWithDelay(uint32_t startOffset) + int dualControlsWithDelay() { static const unsigned int maxDelay = 2; @@ -175,25 +175,24 @@ protected: delayed->reset(); /* Trigger the first frame start event */ - delayed->applyControls(startOffset); + delayed->applyControls(0); /* Test dual control with delay. */ for (unsigned int i = 1; i < 100; i++) { - uint32_t frame = startOffset + i; int32_t value = 10 + i; ctrls.set(V4L2_CID_BRIGHTNESS, value); ctrls.set(V4L2_CID_CONTRAST, value + 1); delayed->push(ctrls); - delayed->applyControls(frame); + delayed->applyControls(i); - ControlList result = delayed->get(frame); + ControlList result = delayed->get(i); int32_t brightness = result.get(V4L2_CID_BRIGHTNESS).get<int32_t>(); int32_t contrast = result.get(V4L2_CID_CONTRAST).get<int32_t>(); if (brightness != expected || contrast != expected + 1) { cerr << "Failed dual controls" - << " frame " << frame + << " frame " << i << " brightness " << brightness << " contrast " << contrast << " expected " << expected @@ -283,17 +282,7 @@ protected: return ret; /* Test dual controls with different delays. */ - ret = dualControlsWithDelay(0); - if (ret) - return ret; - - /* Test dual controls with non-zero sequence start. */ - ret = dualControlsWithDelay(10000); - if (ret) - return ret; - - /* Test dual controls with sequence number wraparound. */ - ret = dualControlsWithDelay(UINT32_MAX - 50); + ret = dualControlsWithDelay(); if (ret) return ret; diff --git a/test/event-dispatcher.cpp b/test/event-dispatcher.cpp index 9b07ab2b..f71c1c0d 100644 --- a/test/event-dispatcher.cpp +++ b/test/event-dispatcher.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * event-dispatcher.cpp - Event dispatcher test + * Event dispatcher test */ #include <chrono> diff --git a/test/event-thread.cpp b/test/event-thread.cpp index ef8a52c3..5499bbf8 100644 --- a/test/event-thread.cpp +++ b/test/event-thread.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * event-thread.cpp - Threaded event test + * Threaded event test */ #include <chrono> @@ -11,6 +11,7 @@ #include <unistd.h> #include <libcamera/base/event_notifier.h> +#include <libcamera/base/object.h> #include <libcamera/base/thread.h> #include <libcamera/base/timer.h> @@ -84,10 +85,17 @@ private: class EventThreadTest : public Test { protected: + int init() + { + thread_.start(); + + handler_ = new EventHandler(); + + return TestPass; + } + int run() { - Thread thread; - thread.start(); /* * Fire the event notifier and then move the notifier to a @@ -97,23 +105,33 @@ protected: * different thread will correctly process already pending * events in the new thread. */ - EventHandler handler; - handler.notify(); - handler.moveToThread(&thread); + handler_->notify(); + handler_->moveToThread(&thread_); this_thread::sleep_for(chrono::milliseconds(100)); - /* Must stop thread before destroying the handler. */ - thread.exit(0); - thread.wait(); - - if (!handler.notified()) { + if (!handler_->notified()) { cout << "Thread event handling test failed" << endl; return TestFail; } return TestPass; } + + void cleanup() + { + /* + * Object class instances must be destroyed from the thread + * they live in. + */ + handler_->deleteLater(); + thread_.exit(0); + thread_.wait(); + } + +private: + EventHandler *handler_; + Thread thread_; }; TEST_REGISTER(EventThreadTest) diff --git a/test/event.cpp b/test/event.cpp index 19dceae1..9f7b1ed4 100644 --- a/test/event.cpp +++ b/test/event.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * event.cpp - Event test + * Event test */ #include <iostream> diff --git a/test/fence.cpp b/test/fence.cpp index 1e38bc2f..ada650ff 100644 --- a/test/fence.cpp +++ b/test/fence.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * fence.cpp - Fence test + * Fence test */ #include <iostream> diff --git a/test/file.cpp b/test/file.cpp index 5c978ebf..170e6ccd 100644 --- a/test/file.cpp +++ b/test/file.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * file.cpp - File I/O operations tests + * File I/O operations tests */ #include <fstream> diff --git a/test/flags.cpp b/test/flags.cpp index 2177e247..85c34788 100644 --- a/test/flags.cpp +++ b/test/flags.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * flags.cpp - Flags tests + * Flags tests */ #include <iostream> diff --git a/test/geometry.cpp b/test/geometry.cpp index 008d51ea..64169206 100644 --- a/test/geometry.cpp +++ b/test/geometry.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * geometry.cpp - Geometry classes tests + * Geometry classes tests */ #include <iostream> diff --git a/test/gstreamer/gstreamer_device_provider_test.cpp b/test/gstreamer/gstreamer_device_provider_test.cpp new file mode 100644 index 00000000..8b8e7cba --- /dev/null +++ b/test/gstreamer/gstreamer_device_provider_test.cpp @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2023, Umang Jain <umang.jain@ideasonboard.com> + * + * GStreamer single stream capture test + */ + +#include <vector> + +#include <libcamera/libcamera.h> +#include <gst/gst.h> + +#include "gstreamer_test.h" +#include "test.h" + +using namespace std; + +class GstreamerDeviceProviderTest : public GstreamerTest, public Test +{ +public: + GstreamerDeviceProviderTest() + : GstreamerTest() + { + } + +protected: + int init() override + { + if (status_ != TestPass) + return status_; + + return TestPass; + } + + int run() override + { + g_autoptr(GstDeviceProvider) provider = NULL; + GList *devices, *l; + std::vector<std::string> cameraNames; + std::unique_ptr<libcamera::CameraManager> cm; + + cm = std::make_unique<libcamera::CameraManager>(); + cm->start(); + for (auto &camera : cm->cameras()) + cameraNames.push_back(camera->id()); + cm->stop(); + cm.reset(); + + provider = gst_device_provider_factory_get_by_name("libcameraprovider"); + devices = gst_device_provider_get_devices(provider); + + for (l = devices; l != NULL; l = g_list_next(l)) { + GstDevice *device = GST_DEVICE(l->data); + g_autofree gchar *gst_name; + bool matched = false; + + g_autoptr(GstElement) element = gst_device_create_element(device, NULL); + g_object_get(element, "camera-name", &gst_name, NULL); + + for (auto name : cameraNames) { + if (strcmp(name.c_str(), gst_name) == 0) { + matched = true; + break; + } + } + + if (!matched) + return TestFail; + } + + g_list_free_full(devices, (GDestroyNotify)gst_object_unref); + + return TestPass; + } +}; + +TEST_REGISTER(GstreamerDeviceProviderTest) diff --git a/test/gstreamer/gstreamer_multi_stream_test.cpp b/test/gstreamer/gstreamer_multi_stream_test.cpp index 112f1dee..263d1e86 100644 --- a/test/gstreamer/gstreamer_multi_stream_test.cpp +++ b/test/gstreamer/gstreamer_multi_stream_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Vedant Paranjape * - * gstreamer_multi_stream_test.cpp - GStreamer multi stream capture test + * GStreamer multi stream capture test */ #include <iostream> @@ -29,7 +29,7 @@ class GstreamerMultiStreamTest : public GstreamerTest, public Test { public: GstreamerMultiStreamTest() - : GstreamerTest() + : GstreamerTest(2) { } @@ -39,24 +39,6 @@ protected: if (status_ != TestPass) return status_; - /* Check if platform supports multistream capture */ - libcamera::CameraManager cm; - cm.start(); - bool cameraFound = false; - for (auto &camera : cm.cameras()) { - if (camera->streams().size() > 1) { - cameraName_ = camera->id(); - cameraFound = true; - cm.stop(); - break; - } - } - - if (!cameraFound) { - cm.stop(); - return TestSkip; - } - const gchar *streamDescription = "queue ! fakesink"; g_autoptr(GError) error = NULL; @@ -88,8 +70,6 @@ protected: int run() override { - g_object_set(libcameraSrc_, "camera-name", cameraName_.c_str(), NULL); - /* Build the pipeline */ gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, stream0_, stream1_, NULL); @@ -124,7 +104,6 @@ protected: } private: - std::string cameraName_; GstElement *stream0_; GstElement *stream1_; }; diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp index a0dd12cf..3ef2d323 100644 --- a/test/gstreamer/gstreamer_single_stream_test.cpp +++ b/test/gstreamer/gstreamer_single_stream_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Vedant Paranjape * - * gstreamer_single_stream_test.cpp - GStreamer single stream capture test + * GStreamer single stream capture test */ #include <iostream> @@ -29,30 +29,21 @@ protected: if (status_ != TestPass) return status_; - const gchar *streamDescription = "videoconvert ! fakesink"; - g_autoptr(GError) error0 = NULL; - stream0_ = gst_parse_bin_from_description_full(streamDescription, TRUE, - NULL, - GST_PARSE_FLAG_FATAL_ERRORS, - &error0); - - if (!stream0_) { - g_printerr("Bin could not be created (%s)\n", error0->message); + fakesink_ = gst_element_factory_make("fakesink", nullptr); + if (!fakesink_) { + g_printerr("Your installation is missing 'fakesink'\n"); return TestFail; } - g_object_ref_sink(stream0_); - - if (createPipeline() != TestPass) - return TestFail; + g_object_ref_sink(fakesink_); - return TestPass; + return createPipeline(); } int run() override { /* Build the pipeline */ - gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, stream0_, NULL); - if (gst_element_link(libcameraSrc_, stream0_) != TRUE) { + gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, fakesink_, nullptr); + if (!gst_element_link(libcameraSrc_, fakesink_)) { g_printerr("Elements could not be linked.\n"); return TestFail; } @@ -68,11 +59,11 @@ protected: void cleanup() override { - g_clear_object(&stream0_); + g_clear_object(&fakesink_); } private: - GstElement *stream0_; + GstElement *fakesink_; }; TEST_REGISTER(GstreamerSingleStreamTest) diff --git a/test/gstreamer/gstreamer_test.cpp b/test/gstreamer/gstreamer_test.cpp index 227a5c37..e8119b85 100644 --- a/test/gstreamer/gstreamer_test.cpp +++ b/test/gstreamer/gstreamer_test.cpp @@ -5,6 +5,10 @@ * libcamera Gstreamer element API tests */ +#include <libcamera/libcamera.h> + +#include <libcamera/base/utils.h> + #include "gstreamer_test.h" #include "test.h" @@ -23,19 +27,18 @@ const char *__asan_default_options() } } -GstreamerTest::GstreamerTest() +GstreamerTest::GstreamerTest(unsigned int numStreams) : pipeline_(nullptr), libcameraSrc_(nullptr) { /* - * GStreamer by default spawns a process to run the - * gst-plugin-scanner helper. If libcamera is compiled with ASan - * enabled, and as GStreamer is most likely not, this causes the - * ASan link order check to fail when gst-plugin-scanner - * dlopen()s the plugin as many libraries will have already been - * loaded by then. Fix this issue by disabling spawning of a - * child helper process when scanning the build directory for - * plugins. - */ + * GStreamer by default spawns a process to run the gst-plugin-scanner + * helper. If libcamera is compiled with ASan enabled, and as GStreamer + * is most likely not, this causes the ASan link order check to fail + * when gst-plugin-scanner dlopen()s the plugin as many libraries will + * have already been loaded by then. Fix this issue by disabling + * spawning of a child helper process when scanning the build directory + * for plugins. + */ gst_registry_fork_set_enabled(false); /* Initialize GStreamer */ @@ -49,25 +52,38 @@ GstreamerTest::GstreamerTest() } /* - * Remove the system libcamera plugin, if any, and add the - * plugin from the build directory. - */ - GstRegistry *registry = gst_registry_get(); - g_autoptr(GstPlugin) plugin = gst_registry_lookup(registry, "libgstlibcamera.so"); - if (plugin) - gst_registry_remove_plugin(registry, plugin); - - std::string path = libcamera::utils::libcameraBuildPath() + "src/gstreamer"; - if (!gst_registry_scan_path(registry, path.c_str())) { - g_printerr("Failed to add plugin to registry\n"); - - status_ = TestFail; + * Atleast one camera should be available with numStreams streams, + * otherwise skip the test entirely. + */ + if (!checkMinCameraStreamsAndSetCameraName(numStreams)) { + status_ = TestSkip; return; } status_ = TestPass; } +bool GstreamerTest::checkMinCameraStreamsAndSetCameraName(unsigned int numStreams) +{ + libcamera::CameraManager cm; + bool cameraFound = false; + + cm.start(); + + for (auto &camera : cm.cameras()) { + if (camera->streams().size() < numStreams) + continue; + + cameraFound = true; + cameraName_ = camera->id(); + break; + } + + cm.stop(); + + return cameraFound; +} + GstreamerTest::~GstreamerTest() { g_clear_object(&pipeline_); @@ -82,12 +98,13 @@ int GstreamerTest::createPipeline() pipeline_ = gst_pipeline_new("test-pipeline"); if (!libcameraSrc_ || !pipeline_) { - g_printerr("Unable to create create pipeline %p.%p\n", + g_printerr("Unable to create pipeline %p.%p\n", libcameraSrc_, pipeline_); return TestFail; } + g_object_set(libcameraSrc_, "camera-name", cameraName_.c_str(), NULL); g_object_ref_sink(libcameraSrc_); return TestPass; diff --git a/test/gstreamer/gstreamer_test.h b/test/gstreamer/gstreamer_test.h index 9869d252..abb37c1b 100644 --- a/test/gstreamer/gstreamer_test.h +++ b/test/gstreamer/gstreamer_test.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Vedant Paranjape * - * gstreamer_test.cpp - GStreamer test base class + * GStreamer test base class */ #pragma once @@ -10,16 +10,12 @@ #include <iostream> #include <unistd.h> -#include <libcamera/base/utils.h> - -#include "libcamera/internal/source_paths.h" - #include <gst/gst.h> class GstreamerTest { public: - GstreamerTest(); + GstreamerTest(unsigned int numStreams = 1); virtual ~GstreamerTest(); protected: @@ -28,7 +24,11 @@ protected: int processEvent(); void printError(GstMessage *msg); + std::string cameraName_; GstElement *pipeline_; GstElement *libcameraSrc_; int status_; + +private: + bool checkMinCameraStreamsAndSetCameraName(unsigned int numStreams); }; diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build index 10058fc5..f3ba5a23 100644 --- a/test/gstreamer/meson.build +++ b/test/gstreamer/meson.build @@ -5,16 +5,17 @@ if not gst_enabled endif gstreamer_tests = [ - ['single_stream_test', 'gstreamer_single_stream_test.cpp'], - ['multi_stream_test', 'gstreamer_multi_stream_test.cpp'], + {'name': 'single_stream_test', 'sources': ['gstreamer_single_stream_test.cpp']}, + {'name': 'multi_stream_test', 'sources': ['gstreamer_multi_stream_test.cpp']}, + {'name': 'device_provider_test', 'sources': ['gstreamer_device_provider_test.cpp']}, ] -gstreamer_dep = dependency('gstreamer-1.0', required: true) +gstreamer_dep = dependency('gstreamer-1.0', required : true) -foreach t : gstreamer_tests - exe = executable(t[0], t[1], 'gstreamer_test.cpp', +foreach test : gstreamer_tests + exe = executable(test['name'], test['sources'], 'gstreamer_test.cpp', dependencies : [libcamera_private, gstreamer_dep], link_with : test_libraries, include_directories : test_includes_internal) - test(t[0], exe, suite : 'gstreamer', is_parallel : false) + test(test['name'], exe, suite : 'gstreamer', is_parallel : false, env : gst_env) endforeach diff --git a/test/hotplug-cameras.cpp b/test/hotplug-cameras.cpp index 5d9260a2..530e9a31 100644 --- a/test/hotplug-cameras.cpp +++ b/test/hotplug-cameras.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Umang Jain <email@uajain.com> * - * hotplug-cameras.cpp - Test cameraAdded/cameraRemoved signals in CameraManager + * Test cameraAdded/cameraRemoved signals in CameraManager */ #include <dirent.h> diff --git a/test/ipa/ipa_interface_test.cpp b/test/ipa/ipa_interface_test.cpp index 3c0df843..e840f6ab 100644 --- a/test/ipa/ipa_interface_test.cpp +++ b/test/ipa/ipa_interface_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * ipa_interface_test.cpp - Test the IPA interface + * Test the IPA interface */ #include <fcntl.h> @@ -16,6 +16,7 @@ #include <libcamera/base/event_dispatcher.h> #include <libcamera/base/event_notifier.h> +#include <libcamera/base/object.h> #include <libcamera/base/thread.h> #include <libcamera/base/timer.h> @@ -52,10 +53,10 @@ protected: ipaManager_ = make_unique<IPAManager>(); /* Create a pipeline handler for vimc. */ - std::vector<PipelineHandlerFactory *> &factories = - PipelineHandlerFactory::factories(); - for (PipelineHandlerFactory *factory : factories) { - if (factory->name() == "PipelineHandlerVimc") { + const std::vector<PipelineHandlerFactoryBase *> &factories = + PipelineHandlerFactoryBase::factories(); + for (const PipelineHandlerFactoryBase *factory : factories) { + if (factory->name() == "vimc") { pipe_ = factory->create(nullptr); break; } @@ -106,7 +107,11 @@ protected: /* Test initialization of IPA module. */ std::string conf = ipa_->configurationFile("vimc.conf"); - int ret = ipa_->init(IPASettings{ conf, "vimc" }); + Flags<ipa::vimc::TestFlag> inFlags; + Flags<ipa::vimc::TestFlag> outFlags; + int ret = ipa_->init(IPASettings{ conf, "vimc" }, + ipa::vimc::IPAOperationInit, + inFlags, &outFlags); if (ret < 0) { cerr << "IPA interface init() failed" << endl; return TestFail; diff --git a/test/ipa/ipa_module_test.cpp b/test/ipa/ipa_module_test.cpp index bd5e0e4c..1c97da32 100644 --- a/test/ipa/ipa_module_test.cpp +++ b/test/ipa/ipa_module_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * ipa_module_test.cpp - Test loading of the VIMC IPA module and verify its info + * Test loading of the VIMC IPA module and verify its info */ #include <iostream> @@ -57,7 +57,7 @@ protected: const struct IPAModuleInfo testInfo = { IPA_MODULE_API_VERSION, 0, - "PipelineHandlerVimc", + "vimc", "vimc", }; diff --git a/test/ipa/meson.build b/test/ipa/meson.build index 7938633e..180b0da0 100644 --- a/test/ipa/meson.build +++ b/test/ipa/meson.build @@ -1,15 +1,15 @@ # SPDX-License-Identifier: CC0-1.0 ipa_test = [ - ['ipa_module_test', 'ipa_module_test.cpp'], - ['ipa_interface_test', 'ipa_interface_test.cpp'], + {'name': 'ipa_module_test', 'sources': ['ipa_module_test.cpp']}, + {'name': 'ipa_interface_test', 'sources': ['ipa_interface_test.cpp']}, ] -foreach t : ipa_test - exe = executable(t[0], [t[1], libcamera_generated_ipa_headers], +foreach test : ipa_test + exe = executable(test['name'], test['sources'], libcamera_generated_ipa_headers, dependencies : libcamera_private, link_with : [libipa, test_libraries], include_directories : [libipa_includes, test_includes_internal]) - test(t[0], exe, suite : 'ipa') + test(test['name'], exe, suite : 'ipa') endforeach diff --git a/test/ipc/meson.build b/test/ipc/meson.build index 2a6cd7fb..8e447d22 100644 --- a/test/ipc/meson.build +++ b/test/ipc/meson.build @@ -1,15 +1,15 @@ # SPDX-License-Identifier: CC0-1.0 ipc_tests = [ - ['unixsocket_ipc', 'unixsocket_ipc.cpp'], - ['unixsocket', 'unixsocket.cpp'], + {'name': 'unixsocket_ipc', 'sources': ['unixsocket_ipc.cpp']}, + {'name': 'unixsocket', 'sources': ['unixsocket.cpp']}, ] -foreach t : ipc_tests - exe = executable(t[0], t[1], +foreach test : ipc_tests + exe = executable(test['name'], test['sources'], dependencies : libcamera_private, link_with : test_libraries, include_directories : test_includes_internal) - test(t[0], exe, suite : 'ipc') + test(test['name'], exe, suite : 'ipc') endforeach diff --git a/test/ipc/unixsocket.cpp b/test/ipc/unixsocket.cpp index 304e613b..f44ab9c9 100644 --- a/test/ipc/unixsocket.cpp +++ b/test/ipc/unixsocket.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * unixsocket.cpp - Unix socket IPC test + * Unix socket IPC test */ #include <algorithm> @@ -431,7 +431,7 @@ private: if (ret) return ret; - timeout.start(200ms); + timeout.start(2s); while (!callDone_) { if (!timeout.isRunning()) { cerr << "Call timeout!" << endl; diff --git a/test/ipc/unixsocket_ipc.cpp b/test/ipc/unixsocket_ipc.cpp index 3ee6017e..df7d9c2b 100644 --- a/test/ipc/unixsocket_ipc.cpp +++ b/test/ipc/unixsocket_ipc.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * unixsocket_ipc.cpp - Unix socket IPC test + * Unix socket IPC test */ #include <algorithm> diff --git a/test/libtest/buffer_source.cpp b/test/libtest/buffer_source.cpp index 1b261697..dde11f36 100644 --- a/test/libtest/buffer_source.cpp +++ b/test/libtest/buffer_source.cpp @@ -72,7 +72,7 @@ int BufferSource::allocate(const StreamConfiguration &config) } format.size = config.size; - format.fourcc = V4L2PixelFormat::fromPixelFormat(config.pixelFormat); + format.fourcc = video->toV4L2PixelFormat(config.pixelFormat); if (video->setFormat(&format)) { std::cout << "Failed to set format on output device" << std::endl; return TestFail; diff --git a/test/libtest/buffer_source.h b/test/libtest/buffer_source.h index 0cc71aa5..495da8a9 100644 --- a/test/libtest/buffer_source.h +++ b/test/libtest/buffer_source.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * buffer_source.h - libcamera camera test helper to create FrameBuffers + * libcamera camera test helper to create FrameBuffers */ #pragma once diff --git a/test/libtest/camera_test.h b/test/libtest/camera_test.h index 0b178bc2..713b503f 100644 --- a/test/libtest/camera_test.h +++ b/test/libtest/camera_test.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * camera_test.h - libcamera camera test base class + * libcamera camera test base class */ #pragma once diff --git a/test/libtest/test.cpp b/test/libtest/test.cpp index af37b4dd..4e03def9 100644 --- a/test/libtest/test.cpp +++ b/test/libtest/test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2018, Google Inc. * - * test.cpp - libcamera test base class + * libcamera test base class */ #include <stdlib.h> diff --git a/test/libtest/test.h b/test/libtest/test.h index 23b07743..3a90885d 100644 --- a/test/libtest/test.h +++ b/test/libtest/test.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2018, Google Inc. * - * test.h - libcamera test base class + * libcamera test base class */ #pragma once diff --git a/test/log/log_api.cpp b/test/log/log_api.cpp index 53118960..0b999738 100644 --- a/test/log/log_api.cpp +++ b/test/log/log_api.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * log.cpp - log API test + * log API test */ #include <algorithm> diff --git a/test/log/log_process.cpp b/test/log/log_process.cpp index 966b80cf..9609e23d 100644 --- a/test/log/log_process.cpp +++ b/test/log/log_process.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * log_process.cpp - Logging in isolated child process test + * Logging in isolated child process test */ #include <fcntl.h> @@ -81,12 +81,13 @@ protected: return TestFail; } - timeout.start(200ms); + timeout.start(2s); while (timeout.isRunning()) dispatcher->processEvents(); if (exitStatus_ != Process::NormalExit) { - cerr << "process did not exit normally" << endl; + cerr << "process did not exit normally: " << exitStatus_ + << endl; return TestFail; } @@ -115,8 +116,11 @@ protected: close(fd); string str(buf); - if (str.find(message) == string::npos) + if (str.find(message) == string::npos) { + cerr << "Received message is not correct (received " + << str.length() << " bytes)" << endl; return TestFail; + } return TestPass; } @@ -136,7 +140,7 @@ private: ProcessManager processManager_; Process proc_; - Process::ExitStatus exitStatus_; + Process::ExitStatus exitStatus_ = Process::NotExited; string logPath_; int exitCode_; int num_; diff --git a/test/log/meson.build b/test/log/meson.build index ac87841a..2298ff84 100644 --- a/test/log/meson.build +++ b/test/log/meson.build @@ -1,15 +1,15 @@ # SPDX-License-Identifier: CC0-1.0 log_test = [ - ['log_api', 'log_api.cpp'], - ['log_process', 'log_process.cpp'], + {'name': 'log_api', 'sources': ['log_api.cpp']}, + {'name': 'log_process', 'sources': ['log_process.cpp']}, ] -foreach t : log_test - exe = executable(t[0], t[1], +foreach test : log_test + exe = executable(test['name'], test['sources'], dependencies : libcamera_private, link_with : test_libraries, include_directories : test_includes_internal) - test(t[0], exe, suite : 'log') + test(test['name'], exe, suite : 'log') endforeach diff --git a/test/media_device/media_device_link_test.cpp b/test/media_device/media_device_link_test.cpp index e11f6b78..31528000 100644 --- a/test/media_device/media_device_link_test.cpp +++ b/test/media_device/media_device_link_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * media_device_link_test.cpp - Tests link handling on VIMC media device + * Tests link handling on VIMC media device */ #include <iostream> diff --git a/test/media_device/media_device_print_test.cpp b/test/media_device/media_device_print_test.cpp index cdec5b8d..63aeed48 100644 --- a/test/media_device/media_device_print_test.cpp +++ b/test/media_device/media_device_print_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2018-2019, Google Inc. * - * media_device_print_test.cpp - Print out media devices + * Print out media devices */ #include <iostream> diff --git a/test/media_device/media_device_test.cpp b/test/media_device/media_device_test.cpp index 1397d123..3e41d0f0 100644 --- a/test/media_device/media_device_test.cpp +++ b/test/media_device/media_device_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * media_device_test.cpp - libcamera media device test base class + * libcamera media device test base class */ #include <iostream> diff --git a/test/media_device/media_device_test.h b/test/media_device/media_device_test.h index 9b226f1a..5223b760 100644 --- a/test/media_device/media_device_test.h +++ b/test/media_device/media_device_test.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * media_device_test.h - libcamera media device test base class + * libcamera media device test base class */ #pragma once diff --git a/test/media_device/meson.build b/test/media_device/meson.build index 83dfe8f1..84966c97 100644 --- a/test/media_device/meson.build +++ b/test/media_device/meson.build @@ -5,20 +5,20 @@ lib_mdev_test_sources = files([ ]) media_device_tests = [ - ['media_device_acquire', 'media_device_acquire.cpp'], - ['media_device_print_test', 'media_device_print_test.cpp'], - ['media_device_link_test', 'media_device_link_test.cpp'], + {'name': 'media_device_acquire', 'sources': ['media_device_acquire.cpp']}, + {'name': 'media_device_print_test', 'sources': ['media_device_print_test.cpp']}, + {'name': 'media_device_link_test', 'sources': ['media_device_link_test.cpp']}, ] lib_mdev_test = static_library('lib_mdev_test', lib_mdev_test_sources, dependencies : libcamera_private, include_directories : test_includes_internal) -foreach t : media_device_tests - exe = executable(t[0], t[1], +foreach test : media_device_tests + exe = executable(test['name'], test['sources'], dependencies : libcamera_private, link_with : [test_libraries, lib_mdev_test], include_directories : test_includes_internal) - test(t[0], exe, suite : 'media_device', is_parallel : false) + test(test['name'], exe, suite : 'media_device', is_parallel : false) endforeach diff --git a/test/meson.build b/test/meson.build index d050bfa1..5ed052ed 100644 --- a/test/meson.build +++ b/test/meson.build @@ -7,6 +7,22 @@ endif test_enabled = true +# When ASan is enabled, find the path to the ASan runtime needed by multiple +# tests. This currently works with gcc only, as clang uses different file names +# depending on the compiler version and target architecture. +asan_enabled = false +asan_runtime_missing = false + +if get_option('b_sanitize').contains('address') + asan_enabled = true + + if cc.get_id() == 'gcc' + asan_runtime = run_command(cc, '-print-file-name=libasan.so', check : true).stdout().strip() + else + asan_runtime_missing = true + endif +endif + subdir('libtest') subdir('camera') @@ -16,7 +32,6 @@ subdir('ipa') subdir('ipc') subdir('log') subdir('media_device') -subdir('pipeline') subdir('process') subdir('py') subdir('serialization') @@ -26,66 +41,89 @@ subdir('v4l2_subdevice') subdir('v4l2_videodevice') public_tests = [ - ['geometry', 'geometry.cpp'], - ['public-api', 'public-api.cpp'], - ['signal', 'signal.cpp'], - ['span', 'span.cpp'], + {'name': 'color-space', 'sources': ['color-space.cpp']}, + {'name': 'geometry', 'sources': ['geometry.cpp']}, + {'name': 'public-api', 'sources': ['public-api.cpp']}, + {'name': 'signal', 'sources': ['signal.cpp']}, + {'name': 'span', 'sources': ['span.cpp']}, + {'name': 'transform', 'sources': ['transform.cpp']}, ] internal_tests = [ - ['bayer-format', 'bayer-format.cpp'], - ['byte-stream-buffer', 'byte-stream-buffer.cpp'], - ['camera-sensor', 'camera-sensor.cpp'], - ['delayed_controls', 'delayed_controls.cpp'], - ['event', 'event.cpp'], - ['event-dispatcher', 'event-dispatcher.cpp'], - ['event-thread', 'event-thread.cpp'], - ['file', 'file.cpp'], - ['flags', 'flags.cpp'], - ['hotplug-cameras', 'hotplug-cameras.cpp'], - ['message', 'message.cpp'], - ['object', 'object.cpp'], - ['object-delete', 'object-delete.cpp'], - ['object-invoke', 'object-invoke.cpp'], - ['pixel-format', 'pixel-format.cpp'], - ['shared-fd', 'shared-fd.cpp'], - ['signal-threads', 'signal-threads.cpp'], - ['threads', 'threads.cpp'], - ['timer', 'timer.cpp'], - ['timer-thread', 'timer-thread.cpp'], - ['unique-fd', 'unique-fd.cpp'], - ['utils', 'utils.cpp'], - ['yaml-parser', 'yaml-parser.cpp'], + {'name': 'bayer-format', 'sources': ['bayer-format.cpp']}, + {'name': 'byte-stream-buffer', 'sources': ['byte-stream-buffer.cpp']}, + {'name': 'camera-sensor', 'sources': ['camera-sensor.cpp']}, + {'name': 'delayed_controls', 'sources': ['delayed_controls.cpp']}, + {'name': 'event', 'sources': ['event.cpp']}, + {'name': 'event-dispatcher', 'sources': ['event-dispatcher.cpp']}, + {'name': 'event-thread', 'sources': ['event-thread.cpp']}, + {'name': 'file', 'sources': ['file.cpp']}, + {'name': 'flags', 'sources': ['flags.cpp']}, + {'name': 'hotplug-cameras', 'sources': ['hotplug-cameras.cpp']}, + {'name': 'message', 'sources': ['message.cpp']}, + {'name': 'object', 'sources': ['object.cpp']}, + {'name': 'object-delete', 'sources': ['object-delete.cpp']}, + {'name': 'object-invoke', 'sources': ['object-invoke.cpp']}, + {'name': 'pixel-format', 'sources': ['pixel-format.cpp']}, + {'name': 'shared-fd', 'sources': ['shared-fd.cpp']}, + {'name': 'signal-threads', 'sources': ['signal-threads.cpp']}, + {'name': 'threads', 'sources': 'threads.cpp', 'dependencies': [libthreads]}, + {'name': 'timer', 'sources': ['timer.cpp']}, + {'name': 'timer-fail', 'sources': ['timer-fail.cpp'], 'should_fail': true}, + {'name': 'timer-thread', 'sources': ['timer-thread.cpp']}, + {'name': 'unique-fd', 'sources': ['unique-fd.cpp']}, + {'name': 'utils', 'sources': ['utils.cpp']}, + {'name': 'yaml-parser', 'sources': ['yaml-parser.cpp']}, ] internal_non_parallel_tests = [ - ['fence', 'fence.cpp'], - ['mapped-buffer', 'mapped-buffer.cpp'], + {'name': 'fence', 'sources': ['fence.cpp']}, + {'name': 'mapped-buffer', 'sources': ['mapped-buffer.cpp']}, ] -foreach t : public_tests - exe = executable(t[0], t[1], - dependencies : libcamera_public, +foreach test : public_tests + deps = [libcamera_public] + if 'dependencies' in test + deps += test['dependencies'] + endif + + exe = executable(test['name'], test['sources'], + dependencies : deps, + implicit_include_directories : false, link_with : test_libraries, include_directories : test_includes_public) - test(t[0], exe) + test(test['name'], exe, should_fail : test.get('should_fail', false)) endforeach -foreach t : internal_tests - exe = executable(t[0], t[1], - dependencies : libcamera_private, +foreach test : internal_tests + deps = [libcamera_private] + if 'dependencies' in test + deps += test['dependencies'] + endif + + exe = executable(test['name'], test['sources'], + dependencies : deps, + implicit_include_directories : false, link_with : test_libraries, include_directories : test_includes_internal) - test(t[0], exe) + test(test['name'], exe, should_fail : test.get('should_fail', false)) endforeach -foreach t : internal_non_parallel_tests - exe = executable(t[0], t[1], - dependencies : libcamera_private, +foreach test : internal_non_parallel_tests + deps = [libcamera_private] + if 'dependencies' in test + deps += test['dependencies'] + endif + + exe = executable(test['name'], test['sources'], + dependencies : deps, + implicit_include_directories : false, link_with : test_libraries, include_directories : test_includes_internal) - test(t[0], exe, is_parallel : false) + test(test['name'], exe, + is_parallel : false, + should_fail : test.get('should_fail', false)) endforeach diff --git a/test/message.cpp b/test/message.cpp index d148a13d..19e6646d 100644 --- a/test/message.cpp +++ b/test/message.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * message.cpp - Messages test + * Messages test */ #include <chrono> @@ -11,6 +11,7 @@ #include <thread> #include <libcamera/base/message.h> +#include <libcamera/base/object.h> #include <libcamera/base/thread.h> #include "test.h" @@ -92,25 +93,6 @@ private: bool success_; }; -class SlowMessageReceiver : public Object -{ -protected: - void message(Message *msg) - { - if (msg->type() != Message::None) { - Object::message(msg); - return; - } - - /* - * Don't access any member of the object here (including the - * vtable) as the object will be deleted by the main thread - * while we're sleeping. - */ - this_thread::sleep_for(chrono::milliseconds(100)); - } -}; - class MessageTest : public Test { protected: @@ -127,16 +109,19 @@ protected: return TestFail; } - MessageReceiver receiver; - receiver.moveToThread(&thread_); + MessageReceiver *receiver = new MessageReceiver(); + receiver->moveToThread(&thread_); thread_.start(); - receiver.postMessage(std::make_unique<Message>(Message::None)); + receiver->postMessage(std::make_unique<Message>(Message::None)); this_thread::sleep_for(chrono::milliseconds(100)); - switch (receiver.status()) { + MessageReceiver::Status status = receiver->status(); + receiver->deleteLater(); + + switch (status) { case MessageReceiver::NoMessage: cout << "No message received" << endl; return TestFail; @@ -148,28 +133,12 @@ protected: } /* - * Test for races between message delivery and object deletion. - * Failures result in assertion errors, there is no need for - * explicit checks. - */ - SlowMessageReceiver *slowReceiver = new SlowMessageReceiver(); - slowReceiver->moveToThread(&thread_); - slowReceiver->postMessage(std::make_unique<Message>(Message::None)); - - this_thread::sleep_for(chrono::milliseconds(10)); - - delete slowReceiver; - - this_thread::sleep_for(chrono::milliseconds(100)); - - /* * Test recursive calls to Thread::dispatchMessages(). Messages * should be delivered correctly, without crashes or memory * leaks. Two messages need to be posted to ensure we don't only * test the simple case of a queue containing a single message. */ - std::unique_ptr<RecursiveMessageReceiver> recursiveReceiver = - std::make_unique<RecursiveMessageReceiver>(); + RecursiveMessageReceiver *recursiveReceiver = new RecursiveMessageReceiver(); recursiveReceiver->moveToThread(&thread_); recursiveReceiver->postMessage(std::make_unique<Message>(Message::None)); @@ -177,7 +146,10 @@ protected: this_thread::sleep_for(chrono::milliseconds(10)); - if (!recursiveReceiver->success()) { + bool success = recursiveReceiver->success(); + recursiveReceiver->deleteLater(); + + if (!success) { cout << "Recursive message delivery failed" << endl; return TestFail; } diff --git a/test/object-delete.cpp b/test/object-delete.cpp index eabefe93..676c3970 100644 --- a/test/object-delete.cpp +++ b/test/object-delete.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * object.cpp - Object deletion tests + * Object deletion tests */ #include <iostream> @@ -33,10 +33,10 @@ public: unsigned int *deleteCount_; }; -class NewThread : public Thread +class DeleterThread : public Thread { public: - NewThread(Object *obj) + DeleterThread(Object *obj) : object_(obj) { } @@ -63,9 +63,9 @@ protected: unsigned int count = 0; TestObject *obj = new TestObject(&count); - NewThread thread(obj); - thread.start(); - thread.wait(); + DeleterThread delThread(obj); + delThread.start(); + delThread.wait(); Thread::current()->dispatchMessages(Message::Type::DeferredDelete); @@ -89,6 +89,26 @@ protected: return TestFail; } + /* + * Test that deleteLater() works properly when called just + * before the object's thread exits. + */ + Thread boundThread; + boundThread.start(); + + count = 0; + obj = new TestObject(&count); + obj->moveToThread(&boundThread); + + obj->deleteLater(); + boundThread.exit(); + boundThread.wait(); + + if (count != 1) { + cout << "Object deletion right before thread exit failed (" << count << ")" << endl; + return TestFail; + } + return TestPass; } }; diff --git a/test/object-invoke.cpp b/test/object-invoke.cpp index b1c0f473..def1e61e 100644 --- a/test/object-invoke.cpp +++ b/test/object-invoke.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * object-invoke.cpp - Cross-thread Object method invocation test + * Cross-thread Object method invocation test */ #include <iostream> diff --git a/test/object.cpp b/test/object.cpp index cbd0d3ec..95dc1ef3 100644 --- a/test/object.cpp +++ b/test/object.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * object.cpp - Object tests + * Object tests */ #include <iostream> diff --git a/test/pipeline/ipu3/ipu3_pipeline_test.cpp b/test/pipeline/ipu3/ipu3_pipeline_test.cpp deleted file mode 100644 index 9e647af5..00000000 --- a/test/pipeline/ipu3/ipu3_pipeline_test.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2019, Google Inc. - * - * ipu3_pipeline_test.cpp - Intel IPU3 pipeline test - */ - -#include <iostream> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include <libcamera/camera.h> -#include <libcamera/camera_manager.h> - -#include "libcamera/internal/device_enumerator.h" -#include "libcamera/internal/media_device.h" -#include "libcamera/internal/media_object.h" - -#include "test.h" - -using namespace std; -using namespace libcamera; - -/* - * Verify that the Intel IPU3 pipeline handler gets matched and cameras - * are enumerated correctly. - * - * The test is supposed to be run on an IPU3 platform, otherwise it gets - * skipped. - * - * The test lists all cameras registered in the system, if any camera is - * available at all. - */ -class IPU3PipelineTest : public Test -{ -protected: - int init(); - int run(); - void cleanup(); - -private: - CameraManager *cameraManager_; - unsigned int sensors_; -}; - -int IPU3PipelineTest::init() -{ - unique_ptr<DeviceEnumerator> enumerator = DeviceEnumerator::create(); - if (!enumerator) { - cerr << "Failed to create device enumerator" << endl; - return TestFail; - } - - if (enumerator->enumerate()) { - cerr << "Failed to enumerate media devices" << endl; - return TestFail; - } - - DeviceMatch imgu_dm("ipu3-imgu"); - DeviceMatch cio2_dm("ipu3-cio2"); - - if (!enumerator->search(imgu_dm)) { - cerr << "Failed to find IPU3 IMGU: test skip" << endl; - return TestSkip; - } - - std::shared_ptr<MediaDevice> cio2 = enumerator->search(cio2_dm); - if (!cio2) { - cerr << "Failed to find IPU3 CIO2: test skip" << endl; - return TestSkip; - } - - /* - * Camera sensor are connected to the CIO2 unit. - * Count how many sensors are connected in the system - * and later verify this matches the number of registered - * cameras. - */ - int ret = cio2->populate(); - if (ret) { - cerr << "Failed to populate media device " << cio2->deviceNode() << endl; - return TestFail; - } - - sensors_ = 0; - const vector<MediaEntity *> &entities = cio2->entities(); - for (MediaEntity *entity : entities) { - if (entity->function() == MEDIA_ENT_F_CAM_SENSOR) - sensors_++; - } - - enumerator.reset(nullptr); - - cameraManager_ = new CameraManager(); - ret = cameraManager_->start(); - if (ret) { - cerr << "Failed to start the CameraManager" << endl; - return TestFail; - } - - return 0; -} - -int IPU3PipelineTest::run() -{ - auto cameras = cameraManager_->cameras(); - for (const std::shared_ptr<Camera> &cam : cameras) - cout << "Found camera '" << cam->id() << "'" << endl; - - if (cameras.size() != sensors_) { - cerr << cameras.size() << " cameras registered, but " << sensors_ - << " were expected" << endl; - return TestFail; - } - - return TestPass; -} - -void IPU3PipelineTest::cleanup() -{ - cameraManager_->stop(); - delete cameraManager_; -} - -TEST_REGISTER(IPU3PipelineTest) diff --git a/test/pipeline/ipu3/meson.build b/test/pipeline/ipu3/meson.build deleted file mode 100644 index 16701080..00000000 --- a/test/pipeline/ipu3/meson.build +++ /dev/null @@ -1,14 +0,0 @@ -# SPDX-License-Identifier: CC0-1.0 - -ipu3_test = [ - ['ipu3_pipeline_test', 'ipu3_pipeline_test.cpp'], -] - -foreach t : ipu3_test - exe = executable(t[0], t[1], - dependencies : libcamera_private, - link_with : test_libraries, - include_directories : test_includes_internal) - - test(t[0], exe, suite : 'ipu3', is_parallel : false) -endforeach diff --git a/test/pipeline/meson.build b/test/pipeline/meson.build deleted file mode 100644 index 6e7901fe..00000000 --- a/test/pipeline/meson.build +++ /dev/null @@ -1,4 +0,0 @@ -# SPDX-License-Identifier: CC0-1.0 - -subdir('ipu3') -subdir('rkisp1') diff --git a/test/pipeline/rkisp1/meson.build b/test/pipeline/rkisp1/meson.build deleted file mode 100644 index 364b5711..00000000 --- a/test/pipeline/rkisp1/meson.build +++ /dev/null @@ -1,14 +0,0 @@ -# SPDX-License-Identifier: CC0-1.0 - -rkisp1_test = [ - ['rkisp1_pipeline_test', 'rkisp1_pipeline_test.cpp'], -] - -foreach t : rkisp1_test - exe = executable(t[0], t[1], - dependencies : libcamera_private, - link_with : test_libraries, - include_directories : test_includes_internal) - - test(t[0], exe, suite : 'rkisp1', is_parallel : false) -endforeach diff --git a/test/pipeline/rkisp1/rkisp1_pipeline_test.cpp b/test/pipeline/rkisp1/rkisp1_pipeline_test.cpp deleted file mode 100644 index acaf3c33..00000000 --- a/test/pipeline/rkisp1/rkisp1_pipeline_test.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2020, Linaro - * - * Based on test/pipeline/ipu3/ipu3_pipeline_test.cpp - * - * rkisp1_pipeline_test.cpp - Rockchip RK3399 rkisp1 pipeline test - */ - -#include <iostream> - -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include <libcamera/camera.h> -#include <libcamera/camera_manager.h> - -#include "libcamera/internal/device_enumerator.h" -#include "libcamera/internal/media_device.h" -#include "libcamera/internal/media_object.h" - -#include "test.h" - -using namespace std; -using namespace libcamera; - -/* - * Verify that the RK3399 pipeline handler gets matched and cameras - * are enumerated correctly. - * - * The test is supposed to be run on rockchip platform. - * - * The test lists all cameras registered in the system, if any camera is - * available at all. - */ -class RKISP1PipelineTest : public Test -{ -protected: - int init(); - int run(); - void cleanup(); - -private: - CameraManager *cameraManager_; - unsigned int sensors_; -}; - -int RKISP1PipelineTest::init() -{ - unique_ptr<DeviceEnumerator> enumerator = DeviceEnumerator::create(); - if (!enumerator) { - cerr << "Failed to create device enumerator" << endl; - return TestFail; - } - - if (enumerator->enumerate()) { - cerr << "Failed to enumerate media devices" << endl; - return TestFail; - } - - DeviceMatch dm("rkisp1"); - - std::shared_ptr<MediaDevice> rkisp1 = enumerator->search(dm); - if (!rkisp1) { - cerr << "Failed to find rkisp1: test skip" << endl; - return TestSkip; - } - - int ret = rkisp1->populate(); - if (ret) { - cerr << "Failed to populate media device " - << rkisp1->deviceNode() << endl; - return TestFail; - } - - sensors_ = 0; - const vector<MediaEntity *> &entities = rkisp1->entities(); - for (MediaEntity *entity : entities) { - if (entity->function() == MEDIA_ENT_F_CAM_SENSOR) - sensors_++; - } - - cameraManager_ = new CameraManager(); - ret = cameraManager_->start(); - if (ret) { - cerr << "Failed to start the CameraManager" << endl; - return TestFail; - } - - return 0; -} - -int RKISP1PipelineTest::run() -{ - auto cameras = cameraManager_->cameras(); - for (const std::shared_ptr<Camera> &cam : cameras) - cout << "Found camera '" << cam->id() << "'" << endl; - - if (cameras.size() != sensors_) { - cerr << cameras.size() << " cameras registered, but " << sensors_ - << " were expected" << endl; - return TestFail; - } - - return TestPass; -} - -void RKISP1PipelineTest::cleanup() -{ - cameraManager_->stop(); - delete cameraManager_; -} - -TEST_REGISTER(RKISP1PipelineTest) diff --git a/test/process/meson.build b/test/process/meson.build index af86b277..a80dc2d9 100644 --- a/test/process/meson.build +++ b/test/process/meson.build @@ -1,14 +1,14 @@ # SPDX-License-Identifier: CC0-1.0 process_tests = [ - ['process_test', 'process_test.cpp'], + {'name': 'process_test', 'sources': ['process_test.cpp']}, ] -foreach t : process_tests - exe = executable(t[0], t[1], +foreach test : process_tests + exe = executable(test['name'], test['sources'], dependencies : libcamera_private, link_with : test_libraries, include_directories : test_includes_internal) - test(t[0], exe, suite : 'process', is_parallel : false) + test(test['name'], exe, suite : 'process', is_parallel : false) endforeach diff --git a/test/process/process_test.cpp b/test/process/process_test.cpp index cb6940c6..e9f5e7e9 100644 --- a/test/process/process_test.cpp +++ b/test/process/process_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * process_test.cpp - Process test + * Process test */ #include <iostream> diff --git a/test/public-api.cpp b/test/public-api.cpp index a1cebcf9..b1336f75 100644 --- a/test/public-api.cpp +++ b/test/public-api.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * public-api.cpp - Public API validation + * Public API validation */ #include <libcamera/libcamera.h> diff --git a/test/py/meson.build b/test/py/meson.build index 2affdbd4..0b679d31 100644 --- a/test/py/meson.build +++ b/test/py/meson.build @@ -4,14 +4,29 @@ if not pycamera_enabled subdir_done() endif +# If ASan is enabled, the link order runtime check will fail as Python is not +# linked to ASan. LD_PRELOAD the ASan runtime if available, or skip the test +# otherwise. + +if asan_runtime_missing + warning('Unable to get path to ASan runtime, Python test disabled') + subdir_done() +endif + pymod = import('python') py3 = pymod.find_installation('python3') pypathdir = meson.project_build_root() / 'src' / 'py' +py_env = ['PYTHONPATH=' + pypathdir] + +if asan_enabled + # Disable leak detection as the Python interpreter is full of leaks. + py_env += ['LD_PRELOAD=' + asan_runtime, 'ASAN_OPTIONS=detect_leaks=0'] +endif test('pyunittests', py3, args : files('unittests.py'), - env : ['PYTHONPATH=' + pypathdir], + env : py_env, suite : 'pybindings', is_parallel : false) diff --git a/test/py/unittests.py b/test/py/unittests.py index 9adc4337..1caea98e 100755 --- a/test/py/unittests.py +++ b/test/py/unittests.py @@ -4,11 +4,9 @@ # Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com> from collections import defaultdict -import errno import gc import libcamera as libcam import selectors -import time import typing import unittest import weakref @@ -18,6 +16,18 @@ class BaseTestCase(unittest.TestCase): def assertZero(self, a, msg=None): self.assertEqual(a, 0, msg) + def assertIsAlive(self, wr, msg='object not alive'): + self.assertIsNotNone(wr(), msg) + + def assertIsDead(self, wr, msg='object not dead'): + self.assertIsNone(wr(), msg) + + def assertIsAllAlive(self, wr_list, msg='object not alive'): + self.assertTrue(all([wr() for wr in wr_list]), msg) + + def assertIsAllDead(self, wr_list, msg='object not dead'): + self.assertTrue(all([not wr() for wr in wr_list]), msg) + class SimpleTestMethods(BaseTestCase): def test_get_ref(self): @@ -28,45 +38,44 @@ class SimpleTestMethods(BaseTestCase): self.assertIsNotNone(cam) wr_cam = weakref.ref(cam) - cm = None + del cm gc.collect() - self.assertIsNotNone(wr_cm()) + self.assertIsAlive(wr_cm) - cam = None + del cam gc.collect() - self.assertIsNone(wr_cm()) - self.assertIsNone(wr_cam()) + self.assertIsDead(wr_cm) + self.assertIsDead(wr_cam) def test_acquire_release(self): cm = libcam.CameraManager.singleton() cam = cm.get('platform/vimc.0 Sensor B') self.assertIsNotNone(cam) - ret = cam.acquire() - self.assertZero(ret) + cam.acquire() - ret = cam.release() - self.assertZero(ret) + cam.release() def test_double_acquire(self): cm = libcam.CameraManager.singleton() cam = cm.get('platform/vimc.0 Sensor B') self.assertIsNotNone(cam) - ret = cam.acquire() - self.assertZero(ret) + cam.acquire() libcam.log_set_level('Camera', 'FATAL') - ret = cam.acquire() - self.assertEqual(ret, -errno.EBUSY) + with self.assertRaises(RuntimeError): + cam.acquire() libcam.log_set_level('Camera', 'ERROR') - ret = cam.release() - self.assertZero(ret) + cam.release() - ret = cam.release() - # I expected EBUSY, but looks like double release works fine - self.assertZero(ret) + # I expected exception here, but looks like double release works fine + cam.release() + + def test_version(self): + cm = libcam.CameraManager.singleton() + self.assertIsInstance(cm.version, str) class CameraTesterBase(BaseTestCase): @@ -80,11 +89,7 @@ class CameraTesterBase(BaseTestCase): self.cm = None self.skipTest('No vimc found') - ret = self.cam.acquire() - if ret != 0: - self.cam = None - self.cm = None - raise Exception('Failed to acquire camera') + self.cam.acquire() self.wr_cam = weakref.ref(self.cam) self.wr_cm = weakref.ref(self.cm) @@ -93,15 +98,13 @@ class CameraTesterBase(BaseTestCase): # If a test fails, the camera may be in running state. So always stop. self.cam.stop() - ret = self.cam.release() - if ret != 0: - raise Exception('Failed to release camera') + self.cam.release() self.cam = None self.cm = None - self.assertIsNone(self.wr_cm()) - self.assertIsNone(self.wr_cam()) + self.assertIsDead(self.wr_cm) + self.assertIsDead(self.wr_cam) class AllocatorTestMethods(CameraTesterBase): @@ -115,49 +118,48 @@ class AllocatorTestMethods(CameraTesterBase): streamconfig = camconfig.at(0) wr_streamconfig = weakref.ref(streamconfig) - ret = cam.configure(camconfig) - self.assertZero(ret) + cam.configure(camconfig) stream = streamconfig.stream wr_stream = weakref.ref(stream) # stream should keep streamconfig and camconfig alive - streamconfig = None - camconfig = None + del streamconfig + del camconfig gc.collect() - self.assertIsNotNone(wr_camconfig()) - self.assertIsNotNone(wr_streamconfig()) + self.assertIsAlive(wr_camconfig) + self.assertIsAlive(wr_streamconfig) allocator = libcam.FrameBufferAllocator(cam) - ret = allocator.allocate(stream) - self.assertTrue(ret > 0) + num_bufs = allocator.allocate(stream) + self.assertTrue(num_bufs > 0) wr_allocator = weakref.ref(allocator) buffers = allocator.buffers(stream) self.assertIsNotNone(buffers) - buffers = None + del buffers buffer = allocator.buffers(stream)[0] self.assertIsNotNone(buffer) wr_buffer = weakref.ref(buffer) - allocator = None + del allocator gc.collect() - self.assertIsNotNone(wr_buffer()) - self.assertIsNotNone(wr_allocator()) - self.assertIsNotNone(wr_stream()) + self.assertIsAlive(wr_buffer) + self.assertIsAlive(wr_allocator) + self.assertIsAlive(wr_stream) - buffer = None + del buffer gc.collect() - self.assertIsNone(wr_buffer()) - self.assertIsNone(wr_allocator()) - self.assertIsNotNone(wr_stream()) + self.assertIsDead(wr_buffer) + self.assertIsDead(wr_allocator) + self.assertIsAlive(wr_stream) - stream = None + del stream gc.collect() - self.assertIsNone(wr_stream()) - self.assertIsNone(wr_camconfig()) - self.assertIsNone(wr_streamconfig()) + self.assertIsDead(wr_stream) + self.assertIsDead(wr_camconfig) + self.assertIsDead(wr_streamconfig) class SimpleCaptureMethods(CameraTesterBase): @@ -173,14 +175,13 @@ class SimpleCaptureMethods(CameraTesterBase): self.assertIsNotNone(fmts) fmts = None - ret = cam.configure(camconfig) - self.assertZero(ret) + cam.configure(camconfig) stream = streamconfig.stream allocator = libcam.FrameBufferAllocator(cam) - ret = allocator.allocate(stream) - self.assertTrue(ret > 0) + num_bufs = allocator.allocate(stream) + self.assertTrue(num_bufs > 0) num_bufs = len(allocator.buffers(stream)) @@ -190,26 +191,30 @@ class SimpleCaptureMethods(CameraTesterBase): self.assertIsNotNone(req) buffer = allocator.buffers(stream)[i] - ret = req.add_buffer(stream, buffer) - self.assertZero(ret) + req.add_buffer(stream, buffer) reqs.append(req) buffer = None - ret = cam.start() - self.assertZero(ret) + cam.start() for req in reqs: - ret = cam.queue_request(req) - self.assertZero(ret) + cam.queue_request(req) reqs = None gc.collect() + sel = selectors.DefaultSelector() + sel.register(cm.event_fd, selectors.EVENT_READ) + reqs = [] while True: + events = sel.select() + if not events: + continue + ready_reqs = cm.get_ready_requests() reqs += ready_reqs @@ -223,8 +228,7 @@ class SimpleCaptureMethods(CameraTesterBase): reqs = None gc.collect() - ret = cam.stop() - self.assertZero(ret) + cam.stop() def test_select(self): cm = self.cm @@ -238,14 +242,13 @@ class SimpleCaptureMethods(CameraTesterBase): self.assertIsNotNone(fmts) fmts = None - ret = cam.configure(camconfig) - self.assertZero(ret) + cam.configure(camconfig) stream = streamconfig.stream allocator = libcam.FrameBufferAllocator(cam) - ret = allocator.allocate(stream) - self.assertTrue(ret > 0) + num_bufs = allocator.allocate(stream) + self.assertTrue(num_bufs > 0) num_bufs = len(allocator.buffers(stream)) @@ -255,19 +258,16 @@ class SimpleCaptureMethods(CameraTesterBase): self.assertIsNotNone(req) buffer = allocator.buffers(stream)[i] - ret = req.add_buffer(stream, buffer) - self.assertZero(ret) + req.add_buffer(stream, buffer) reqs.append(req) buffer = None - ret = cam.start() - self.assertZero(ret) + cam.start() for req in reqs: - ret = cam.queue_request(req) - self.assertZero(ret) + cam.queue_request(req) reqs = None gc.collect() @@ -280,7 +280,7 @@ class SimpleCaptureMethods(CameraTesterBase): running = True while running: events = sel.select() - for key, _ in events: + for _ in events: ready_reqs = cm.get_ready_requests() reqs += ready_reqs @@ -296,8 +296,7 @@ class SimpleCaptureMethods(CameraTesterBase): reqs = None gc.collect() - ret = cam.stop() - self.assertZero(ret) + cam.stop() # Recursively expand slist's objects into olist, using seen to track already diff --git a/test/serialization/control_serialization.cpp b/test/serialization/control_serialization.cpp index a507d98a..06c572b7 100644 --- a/test/serialization/control_serialization.cpp +++ b/test/serialization/control_serialization.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * control_serialization.cpp - Serialize and deserialize controls + * Serialize and deserialize controls */ #include <iostream> diff --git a/test/serialization/generated_serializer/generated_serializer_test.cpp b/test/serialization/generated_serializer/generated_serializer_test.cpp index 698c81d6..4b11d67f 100644 --- a/test/serialization/generated_serializer/generated_serializer_test.cpp +++ b/test/serialization/generated_serializer/generated_serializer_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * generated_serializer_test.cpp - Test generated serializer + * Test generated serializer */ #include <algorithm> @@ -35,6 +35,13 @@ if (struct1.field != struct2.field) { \ return TestFail; \ } +#define TEST_SCOPED_ENUM_EQUALITY(struct1, struct2, field) \ +if (struct1.field != struct2.field) { \ + cerr << #field << " field incorrect" << endl; \ + return TestFail; \ +} + + ipa::test::TestStruct t, u; t.m = { @@ -51,6 +58,13 @@ if (struct1.field != struct2.field) { \ t.s2 = "goodbye"; t.s3 = "lorem ipsum"; t.i = 58527; + t.c = ipa::test::IPAOperationInit; + t.e = ipa::test::ErrorFlags::Error1; + + Flags<ipa::test::ErrorFlags> flags; + flags |= ipa::test::ErrorFlags::Error1; + flags |= ipa::test::ErrorFlags::Error2; + t.f = flags; std::vector<uint8_t> serialized; @@ -69,7 +83,10 @@ if (struct1.field != struct2.field) { \ TEST_FIELD_EQUALITY(t, u, s2); TEST_FIELD_EQUALITY(t, u, s3); TEST_FIELD_EQUALITY(t, u, i); + TEST_FIELD_EQUALITY(t, u, c); + TEST_SCOPED_ENUM_EQUALITY(t, u, e); + TEST_SCOPED_ENUM_EQUALITY(t, u, f); /* Test vector of generated structs */ std::vector<ipa::test::TestStruct> v = { t, u }; @@ -92,11 +109,19 @@ if (struct1.field != struct2.field) { \ TEST_FIELD_EQUALITY(v[0], w[0], s2); TEST_FIELD_EQUALITY(v[0], w[0], s3); TEST_FIELD_EQUALITY(v[0], w[0], i); + TEST_FIELD_EQUALITY(v[0], w[0], c); + + TEST_SCOPED_ENUM_EQUALITY(v[0], w[0], e); + TEST_SCOPED_ENUM_EQUALITY(v[0], w[0], f); TEST_FIELD_EQUALITY(v[1], w[1], s1); TEST_FIELD_EQUALITY(v[1], w[1], s2); TEST_FIELD_EQUALITY(v[1], w[1], s3); TEST_FIELD_EQUALITY(v[1], w[1], i); + TEST_FIELD_EQUALITY(v[1], w[1], c); + + TEST_SCOPED_ENUM_EQUALITY(v[1], w[1], e); + TEST_SCOPED_ENUM_EQUALITY(v[1], w[1], f); return TestPass; } diff --git a/test/serialization/generated_serializer/include/libcamera/ipa/test.mojom b/test/serialization/generated_serializer/include/libcamera/ipa/test.mojom index 5f200885..91c31642 100644 --- a/test/serialization/generated_serializer/include/libcamera/ipa/test.mojom +++ b/test/serialization/generated_serializer/include/libcamera/ipa/test.mojom @@ -9,6 +9,13 @@ enum IPAOperationCode { IPAOperationStop, }; +[scopedEnum] enum ErrorFlags { + Error1 = 0x1, + Error2 = 0x2, + Error3 = 0x4, + Error4 = 0x8, +}; + struct IPASettings {}; struct TestStruct { @@ -18,6 +25,9 @@ struct TestStruct { string s2; int32 i; string s3; + IPAOperationCode c; + ErrorFlags e; + [flags] ErrorFlags f; }; interface IPATestInterface { diff --git a/test/serialization/ipa_data_serializer_test.cpp b/test/serialization/ipa_data_serializer_test.cpp index d2050a86..aea63c73 100644 --- a/test/serialization/ipa_data_serializer_test.cpp +++ b/test/serialization/ipa_data_serializer_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * ipa_data_serializer_test.cpp - Test serializing/deserializing with IPADataSerializer + * Test serializing/deserializing with IPADataSerializer */ #include <algorithm> @@ -20,11 +20,7 @@ #include <libcamera/base/thread.h> #include <libcamera/base/timer.h> -#include "libcamera/internal/device_enumerator.h" #include "libcamera/internal/ipa_data_serializer.h" -#include "libcamera/internal/ipa_manager.h" -#include "libcamera/internal/ipa_module.h" -#include "libcamera/internal/pipeline_handler.h" #include "serialization_test.h" #include "test.h" diff --git a/test/serialization/meson.build b/test/serialization/meson.build index 26e42b15..a6e8d793 100644 --- a/test/serialization/meson.build +++ b/test/serialization/meson.build @@ -3,14 +3,14 @@ subdir('generated_serializer') serialization_tests = [ - ['control_serialization', 'control_serialization.cpp'], - ['ipa_data_serializer_test', 'ipa_data_serializer_test.cpp'], + {'name': 'control_serialization', 'sources': ['control_serialization.cpp']}, + {'name': 'ipa_data_serializer_test', 'sources': ['ipa_data_serializer_test.cpp']}, ] -foreach t : serialization_tests - exe = executable(t[0], [t[1], 'serialization_test.cpp'], +foreach test : serialization_tests + exe = executable(test['name'], test['sources'], 'serialization_test.cpp', dependencies : libcamera_private, link_with : test_libraries, include_directories : test_includes_internal) - test(t[0], exe, suite : 'serialization', is_parallel : false) + test(test['name'], exe, suite : 'serialization', is_parallel : false) endforeach diff --git a/test/serialization/serialization_test.cpp b/test/serialization/serialization_test.cpp index 11d0f0f3..af9969fd 100644 --- a/test/serialization/serialization_test.cpp +++ b/test/serialization/serialization_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * serialization_test.cpp - Base class for serialization tests + * Base class for serialization tests */ #include "serialization_test.h" diff --git a/test/serialization/serialization_test.h b/test/serialization/serialization_test.h index 609f9fdf..760e3721 100644 --- a/test/serialization/serialization_test.h +++ b/test/serialization/serialization_test.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * serialization_test.h - Base class for serialization tests + * Base class for serialization tests */ #pragma once diff --git a/test/shared-fd.cpp b/test/shared-fd.cpp index 997d7be1..57199dfe 100644 --- a/test/shared-fd.cpp +++ b/test/shared-fd.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * shared_fd.cpp - SharedFD test + * SharedFD test */ #include <fcntl.h> diff --git a/test/signal-threads.cpp b/test/signal-threads.cpp index d5e2eb66..c4789c83 100644 --- a/test/signal-threads.cpp +++ b/test/signal-threads.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * signal-threads.cpp - Cross-thread signal delivery test + * Cross-thread signal delivery test */ #include <chrono> @@ -10,6 +10,7 @@ #include <thread> #include <libcamera/base/message.h> +#include <libcamera/base/object.h> #include <libcamera/base/thread.h> #include <libcamera/base/utils.h> @@ -58,15 +59,20 @@ private: class SignalThreadsTest : public Test { protected: - int run() + int init() { - SignalReceiver receiver; - signal_.connect(&receiver, &SignalReceiver::slot); + receiver_ = new SignalReceiver(); + signal_.connect(receiver_, &SignalReceiver::slot); + + return TestPass; + } + int run() + { /* Test that a signal is received in the main thread. */ signal_.emit(0); - switch (receiver.status()) { + switch (receiver_->status()) { case SignalReceiver::NoSignal: cout << "No signal received for direct connection" << endl; return TestFail; @@ -82,8 +88,8 @@ protected: * Move the object to a thread and verify that the signal is * correctly delivered, with the correct data. */ - receiver.reset(); - receiver.moveToThread(&thread_); + receiver_->reset(); + receiver_->moveToThread(&thread_); thread_.start(); @@ -91,7 +97,7 @@ protected: this_thread::sleep_for(chrono::milliseconds(100)); - switch (receiver.status()) { + switch (receiver_->status()) { case SignalReceiver::NoSignal: cout << "No signal received for message connection" << endl; return TestFail; @@ -103,7 +109,7 @@ protected: break; } - if (receiver.value() != 42) { + if (receiver_->value() != 42) { cout << "Signal received with incorrect value" << endl; return TestFail; } @@ -113,11 +119,13 @@ protected: void cleanup() { + receiver_->deleteLater(); thread_.exit(0); thread_.wait(); } private: + SignalReceiver *receiver_; Thread thread_; Signal<int> signal_; diff --git a/test/signal.cpp b/test/signal.cpp index 5c6b304d..3f596b22 100644 --- a/test/signal.cpp +++ b/test/signal.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * signal.cpp - Signal test + * Signal test */ #include <iostream> diff --git a/test/span.cpp b/test/span.cpp index abf3a5d6..5452967d 100644 --- a/test/span.cpp +++ b/test/span.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * span.cpp - Span tests + * Span tests */ /* diff --git a/test/stream/meson.build b/test/stream/meson.build index 73608ffd..dd77f2f7 100644 --- a/test/stream/meson.build +++ b/test/stream/meson.build @@ -1,13 +1,14 @@ # SPDX-License-Identifier: CC0-1.0 stream_tests = [ - ['stream_formats', 'stream_formats.cpp'], + {'name': 'stream_colorspace', 'sources': ['stream_colorspace.cpp']}, + {'name': 'stream_formats', 'sources': ['stream_formats.cpp']}, ] -foreach t : stream_tests - exe = executable(t[0], t[1], +foreach test : stream_tests + exe = executable(test['name'], test['sources'], dependencies : libcamera_public, link_with : test_libraries, include_directories : test_includes_internal) - test(t[0], exe, suite: 'stream') + test(test['name'], exe, suite : 'stream') endforeach diff --git a/test/stream/stream_colorspace.cpp b/test/stream/stream_colorspace.cpp new file mode 100644 index 00000000..4c904c4c --- /dev/null +++ b/test/stream/stream_colorspace.cpp @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2022, Ideas on Board Oy. + * + * Stream colorspace adjustment test + */ + +#include <iostream> + +#include <libcamera/camera.h> +#include <libcamera/formats.h> +#include <libcamera/stream.h> + +#include "test.h" + +using namespace libcamera; +using namespace std; + +class TestCameraConfiguration : public CameraConfiguration +{ +public: + TestCameraConfiguration() + : CameraConfiguration() + { + } + + Status validate() override + { + return validateColorSpaces(); + } +}; + +class StreamColorSpaceTest : public Test +{ +protected: + int run() + { + TestCameraConfiguration config; + + StreamConfiguration cfg; + cfg.size = { 640, 320 }; + cfg.pixelFormat = formats::YUV422; + cfg.colorSpace = ColorSpace::Srgb; + config.addConfiguration(cfg); + + StreamConfiguration &streamCfg = config.at(0); + + /* + * YUV pixelformat with sRGB colorspace should have Y'CbCr encoding + * adjusted. + */ + config.validate(); + if (streamCfg.colorSpace->ycbcrEncoding == ColorSpace::YcbcrEncoding::None) { + cerr << "YUV format must have YCbCr encoding" << endl; + return TestFail; + } + + /* + * For YUV pixelFormat, encoding should be picked up according + * to primaries and transfer function, if 'None' is specified. + */ + streamCfg.pixelFormat = formats::YUV422; + streamCfg.colorSpace = ColorSpace(ColorSpace::Primaries::Rec2020, + ColorSpace::TransferFunction::Rec709, + ColorSpace::YcbcrEncoding::None, + ColorSpace::Range::Limited); + config.validate(); + if (streamCfg.colorSpace->ycbcrEncoding != ColorSpace::YcbcrEncoding::Rec2020) { + cerr << "Failed to adjust colorspace Y'CbCr encoding according" + << " to primaries and transfer function" << endl; + return TestFail; + } + + /* For RGB pixelFormat, Sycc colorspace should get adjusted to sRGB. */ + streamCfg.pixelFormat = formats::RGB888; + streamCfg.colorSpace = ColorSpace::Sycc; + config.validate(); + if (streamCfg.colorSpace != ColorSpace::Srgb) { + cerr << "RGB format's colorspace should be set to Srgb" << endl; + return TestFail; + } + + /* Raw formats should always set colorspace to ColorSpace::Raw. */ + streamCfg.pixelFormat = formats::SBGGR8; + streamCfg.colorSpace = ColorSpace::Rec709; + config.validate(); + if (streamCfg.colorSpace != ColorSpace::Raw) { + cerr << "Raw format must always have Raw colorspace" << endl; + return TestFail; + } + + return TestPass; + } +}; + +TEST_REGISTER(StreamColorSpaceTest) diff --git a/test/stream/stream_formats.cpp b/test/stream/stream_formats.cpp index 99fa0385..553b59aa 100644 --- a/test/stream/stream_formats.cpp +++ b/test/stream/stream_formats.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * stream_formats.cpp - StreamFormats test + * StreamFormats test */ #include <iostream> diff --git a/test/threads.cpp b/test/threads.cpp index d83b5833..ceb4fa0f 100644 --- a/test/threads.cpp +++ b/test/threads.cpp @@ -2,13 +2,15 @@ /* * Copyright (C) 2019, Google Inc. * - * threads.cpp - Threads test + * Threads test */ #include <chrono> #include <iostream> #include <memory> +#include <pthread.h> #include <thread> +#include <time.h> #include <libcamera/base/thread.h> @@ -35,6 +37,35 @@ private: chrono::steady_clock::duration duration_; }; +class CancelThread : public Thread +{ +public: + CancelThread(bool &cancelled) + : cancelled_(cancelled) + { + } + +protected: + void run() + { + cancelled_ = true; + + /* + * Cancel the thread and call a guaranteed cancellation point + * (nanosleep). + */ + pthread_cancel(pthread_self()); + + struct timespec req{ 0, 100*000*000 }; + nanosleep(&req, nullptr); + + cancelled_ = false; + } + +private: + bool &cancelled_; +}; + class ThreadTest : public Test { protected: @@ -118,6 +149,22 @@ protected: return TestFail; } + /* Test thread cleanup upon abnormal termination. */ + bool cancelled = false; + bool finished = false; + + thread = std::make_unique<CancelThread>(cancelled); + thread->finished.connect(this, [&finished]() { finished = true; }); + + thread->start(); + thread->exit(0); + thread->wait(chrono::milliseconds(1000)); + + if (!cancelled || !finished) { + cout << "Cleanup failed upon abnormal termination" << endl; + return TestFail; + } + return TestPass; } diff --git a/test/timer-fail.cpp b/test/timer-fail.cpp new file mode 100644 index 00000000..0ced6441 --- /dev/null +++ b/test/timer-fail.cpp @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2024, Ideas on Board Oy + * + * Threaded timer failure test + */ + +#include <chrono> +#include <iostream> + +#include <libcamera/base/event_dispatcher.h> +#include <libcamera/base/object.h> +#include <libcamera/base/thread.h> +#include <libcamera/base/timer.h> + +#include "test.h" + +using namespace libcamera; +using namespace std; +using namespace std::chrono_literals; + +class TimeoutHandler : public Object +{ +public: + TimeoutHandler() + : timer_(this), timeout_(false) + { + timer_.timeout.connect(this, &TimeoutHandler::timeoutHandler); + } + + void start() + { + timer_.start(100ms); + } + + bool timeout() const + { + return timeout_; + } + +private: + void timeoutHandler() + { + timeout_ = true; + } + + Timer timer_; + bool timeout_; +}; + +class TimerFailTest : public Test +{ +protected: + int init() + { + thread_.start(); + + timeout_ = new TimeoutHandler(); + timeout_->moveToThread(&thread_); + + return TestPass; + } + + int run() + { + /* + * Test that the forbidden operation of starting the timer from + * another thread results in a failure. We need to interrupt the + * event dispatcher to make sure we don't succeed simply because + * the event dispatcher hasn't noticed the timer restart. + */ + timeout_->start(); + thread_.eventDispatcher()->interrupt(); + + this_thread::sleep_for(chrono::milliseconds(200)); + + /* + * The wrong start() call should result in an assertion in debug + * builds, and a timeout in release builds. The test is + * therefore marked in meson.build as expected to fail. We need + * to return TestPass in the unexpected (usually known as + * "fail") case, and TestFail otherwise. + */ + if (timeout_->timeout()) { + cout << "Timer start from wrong thread succeeded unexpectedly" + << endl; + return TestPass; + } + + return TestFail; + } + + void cleanup() + { + /* + * Object class instances must be destroyed from the thread + * they live in. + */ + timeout_->deleteLater(); + thread_.exit(0); + thread_.wait(); + } + +private: + TimeoutHandler *timeout_; + Thread thread_; +}; + +TEST_REGISTER(TimerFailTest) diff --git a/test/timer-thread.cpp b/test/timer-thread.cpp index 61821753..55e5cfdf 100644 --- a/test/timer-thread.cpp +++ b/test/timer-thread.cpp @@ -2,13 +2,14 @@ /* * Copyright (C) 2019, Google Inc. * - * timer-thread.cpp - Threaded timer test + * Threaded timer test */ #include <chrono> #include <iostream> #include <libcamera/base/event_dispatcher.h> +#include <libcamera/base/object.h> #include <libcamera/base/thread.h> #include <libcamera/base/timer.h> @@ -28,12 +29,6 @@ public: timer_.start(100ms); } - void restart() - { - timeout_ = false; - timer_.start(100ms); - } - bool timeout() const { return timeout_; @@ -55,7 +50,9 @@ protected: int init() { thread_.start(); - timeout_.moveToThread(&thread_); + + timeout_ = new TimeoutHandler(); + timeout_->moveToThread(&thread_); return TestPass; } @@ -68,39 +65,27 @@ protected: */ this_thread::sleep_for(chrono::milliseconds(200)); - if (!timeout_.timeout()) { + if (!timeout_->timeout()) { cout << "Timer expiration test failed" << endl; return TestFail; } - /* - * Test that starting the timer from another thread fails. We - * need to interrupt the event dispatcher to make sure we don't - * succeed simply because the event dispatcher hasn't noticed - * the timer restart. - */ - timeout_.restart(); - thread_.eventDispatcher()->interrupt(); - - this_thread::sleep_for(chrono::milliseconds(200)); - - if (timeout_.timeout()) { - cout << "Timer restart test failed" << endl; - return TestFail; - } - return TestPass; } void cleanup() { - /* Must stop thread before destroying timeout. */ + /* + * Object class instances must be destroyed from the thread + * they live in. + */ + timeout_->deleteLater(); thread_.exit(0); thread_.wait(); } private: - TimeoutHandler timeout_; + TimeoutHandler *timeout_; Thread thread_; }; diff --git a/test/timer.cpp b/test/timer.cpp index 0f01c3cb..2eacc059 100644 --- a/test/timer.cpp +++ b/test/timer.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * timer.cpp - Timer test + * Timer test */ #include <chrono> diff --git a/test/transform.cpp b/test/transform.cpp new file mode 100644 index 00000000..4ec7a1eb --- /dev/null +++ b/test/transform.cpp @@ -0,0 +1,329 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2023, Ideas On Board Oy + * + * Transform and Orientation tests + */ + +#include <iostream> + +#include <libcamera/orientation.h> +#include <libcamera/transform.h> + +#include "test.h" + +using namespace std; +using namespace libcamera; + +class TransformTest : public Test +{ +protected: + int run(); +}; + +int TransformTest::run() +{ + /* + * RotationTestEntry collects two Orientation and one Transform that + * gets combined to validate that (o1 / o2 = T) and (o1 = o2 * T) + * + * o1 / o2 = t computes the Transform to apply to o2 to obtain o1 + * o2 * t = o1 combines o2 with t by applying o2 first then t + * + * The comments on the (most complex) transform show how applying to + * an image with orientation o2 the Transform t allows to obtain o1. + * + * The image with basic rotation0 is assumed to be: + * + * AB + * CD + * + * And the Transform operators are: + * + * V = vertical flip + * H = horizontal flip + * T = transpose + * + * the operator '* (T|V)' applies V first then T. + */ + static const struct RotationTestEntry { + Orientation o1; + Orientation o2; + Transform t; + } testEntries[] = { + /* Test identities transforms first. */ + { + Orientation::Rotate0, Orientation::Rotate0, + Transform::Identity, + }, + { + Orientation::Rotate0Mirror, Orientation::Rotate0Mirror, + Transform::Identity, + }, + { + Orientation::Rotate180, Orientation::Rotate180, + Transform::Identity, + }, + { + Orientation::Rotate180Mirror, Orientation::Rotate180Mirror, + Transform::Identity, + }, + { + Orientation::Rotate90, Orientation::Rotate90, + Transform::Identity, + }, + { + Orientation::Rotate90Mirror, Orientation::Rotate90Mirror, + Transform::Identity, + }, + { + Orientation::Rotate270, Orientation::Rotate270, + Transform::Identity, + }, + { + Orientation::Rotate270Mirror, Orientation::Rotate270Mirror, + Transform::Identity, + }, + /* + * Combine 0 and 180 degrees rotation as they're the most common + * ones. + */ + { + /* + * o2 t o1 + * -------------------------- + * CD * (H|V) = BA AB + * BA CD CD + */ + Orientation::Rotate0, Orientation::Rotate180, + Transform::Rot180, + }, + { + /* + * o2 t o1 + * -------------------------- + * AB * (H|V) = CD DC + * CD AB BA + */ + Orientation::Rotate180, Orientation::Rotate0, + Transform::Rot180 + }, + /* Test that transpositions are handled correctly. */ + { + /* + * o2 t o1 + * -------------------------- + * AB * (T|V) = CD CA + * CD AB DB + */ + Orientation::Rotate90, Orientation::Rotate0, + Transform::Rot90, + }, + { + /* + * o2 t o1 + * -------------------------- + * CA * (T|H) = AC AB + * DB BD CD + */ + Orientation::Rotate0, Orientation::Rotate90, + Transform::Rot270, + }, + { + /* + * o2 t o1 + * -------------------------- + * AB * (T|H) = BA BD + * CD DC AC + */ + Orientation::Rotate270, Orientation::Rotate0, + Transform::Rot270, + }, + { + /* + * o2 t o1 + * -------------------------- + * BD * (T|V) = AC AB + * AC BD CD + */ + Orientation::Rotate0, Orientation::Rotate270, + Transform::Rot90, + }, + { + /* + * o2 t o1 + * -------------------------- + * CD * (T|H) = DC DA + * BA AB CB + */ + Orientation::Rotate90, Orientation::Rotate180, + Transform::Rot270, + }, + { + /* + * o2 t o1 + * -------------------------- + * DA * (T|V) = CB CD + * CB DA BA + */ + Orientation::Rotate180, Orientation::Rotate90, + Transform::Rot90, + }, + { + /* + * o2 t o1 + * -------------------------- + * CD * (T|V) = BA BC + * BA CD AD + */ + Orientation::Rotate270, Orientation::Rotate180, + Transform::Rot90, + }, + { + /* + * o2 t o1 + * -------------------------- + * BC * (T|H) = CB CD + * AD DA BA + */ + Orientation::Rotate180, Orientation::Rotate270, + Transform::Rot270, + }, + { + /* + * o2 t o1 + * -------------------------- + * DA * (V|H) = AD BC + * CB BC AD + */ + Orientation::Rotate270, Orientation::Rotate90, + Transform::Rot180, + }, + /* Test that mirroring is handled correctly. */ + { + Orientation::Rotate0, Orientation::Rotate0Mirror, + Transform::HFlip + }, + { + Orientation::Rotate0Mirror, Orientation::Rotate0, + Transform::HFlip + }, + { + Orientation::Rotate180, Orientation::Rotate180Mirror, + Transform::HFlip + }, + { + Orientation::Rotate180Mirror, Orientation::Rotate180, + Transform::HFlip + }, + { + Orientation::Rotate90, Orientation::Rotate90Mirror, + Transform::HFlip + }, + { + Orientation::Rotate90Mirror, Orientation::Rotate90, + Transform::HFlip + }, + { + Orientation::Rotate270, Orientation::Rotate270Mirror, + Transform::HFlip + }, + { + Orientation::Rotate270Mirror, Orientation::Rotate270, + Transform::HFlip + }, + { + Orientation::Rotate0, Orientation::Rotate0Mirror, + Transform::HFlip + }, + /* + * More exotic transforms which include Transpositions and + * mirroring. + */ + { + /* + * o2 t o1 + * ------------------ + * BC * (V) = AD + * AD BC + */ + Orientation::Rotate90Mirror, Orientation::Rotate270, + Transform::VFlip, + }, + { + /* + * o2 t o1 + * ------------------ + * CB * (T) = CD + * DA BA + */ + Orientation::Rotate180, Orientation::Rotate270Mirror, + Transform::Transpose, + }, + { + /* + * o2 t o1 + * ------------------ + * AD * (T) = AB + * BC DC + */ + Orientation::Rotate0, Orientation::Rotate90Mirror, + Transform::Transpose, + }, + { + /* + * o2 t o1 + * ------------------ + * AD * (V) = BC + * BC AD + */ + Orientation::Rotate270, Orientation::Rotate90Mirror, + Transform::VFlip, + }, + { + /* + * o2 t o1 + * ------------------ + * DA * (V) = CB + * CB DA + */ + Orientation::Rotate270Mirror, Orientation::Rotate90, + Transform::VFlip, + }, + { + /* + * o2 t o1 + * -------------------------- + * CB * (V|H) = BC AD + * DA AD BC + */ + Orientation::Rotate90Mirror, Orientation::Rotate270Mirror, + Transform::Rot180, + }, + }; + + for (const auto &entry : testEntries) { + Transform transform = entry.o1 / entry.o2; + if (transform != entry.t) { + cerr << "Failed to validate: " << entry.o1 + << " / " << entry.o2 + << " = " << transformToString(entry.t) << endl; + cerr << "Got back: " + << transformToString(transform) << endl; + return TestFail; + } + + Orientation adjusted = entry.o2 * entry.t; + if (adjusted != entry.o1) { + cerr << "Failed to validate: " << entry.o2 + << " * " << transformToString(entry.t) + << " = " << entry.o1 << endl; + cerr << "Got back: " << adjusted << endl; + return TestFail; + } + } + + return TestPass; +} + +TEST_REGISTER(TransformTest) diff --git a/test/unique-fd.cpp b/test/unique-fd.cpp index eb3b591f..e556439e 100644 --- a/test/unique-fd.cpp +++ b/test/unique-fd.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * unique-fd.cpp - UniqueFD test + * UniqueFD test */ #include <fcntl.h> diff --git a/test/utils.cpp b/test/utils.cpp index d65467b5..41af954b 100644 --- a/test/utils.cpp +++ b/test/utils.cpp @@ -2,11 +2,12 @@ /* * Copyright (C) 2018, Google Inc. * - * utils.cpp - Miscellaneous utility tests + * Miscellaneous utility tests */ #include <iostream> #include <map> +#include <optional> #include <sstream> #include <string> #include <vector> @@ -226,6 +227,14 @@ protected: return TestFail; } + const auto &split = utils::split(path, ":"); + dirs = std::vector<std::string>{ split.begin(), split.end() }; + + if (dirs != elements) { + cerr << "utils::split() LegacyInputIterator test failed" << endl; + return TestFail; + } + /* utils::join() with conversion function test. */ std::vector<Size> sizes = { { 0, 0 }, { 100, 100 } }; s = utils::join(sizes, "/", [](const Size &size) { diff --git a/test/v4l2_compat/meson.build b/test/v4l2_compat/meson.build index 87809589..2691eacf 100644 --- a/test/v4l2_compat/meson.build +++ b/test/v4l2_compat/meson.build @@ -1,20 +1,29 @@ # SPDX-License-Identifier: CC0-1.0 -# If ASan is enabled, the link order runtime check will fail as v4l2-ctl and -# v4l2-compliance are not linked to ASan. Skip the test in that case. -# -# TODO: Find a way to LD_PRELOAD the ASan dynamic library instead, in a -# cross-platform way with support for both gcc and clang. +if not is_variable('v4l2_compat') + subdir_done() +endif + +# If ASan is enabled but the ASan runtime shared library is missing, +# v4l2_compat_test.py won't be able to LD_PRELOAD it, resulting in a link order +# runtime check failure as v4l2-ctl and v4l2-compliance are not linked to ASan. +# Skip the test in that case. -if get_option('b_sanitize').contains('address') +if asan_runtime_missing + warning('Unable to get path to ASan runtime, v4l2_compat test disabled') subdir_done() endif -if is_variable('v4l2_compat') - v4l2_compat_test = files('v4l2_compat_test.py') +v4l2_compat_test = files('v4l2_compat_test.py') +v4l2_compat_args = [] - test('v4l2_compat_test', v4l2_compat_test, - args : v4l2_compat, - suite : 'v4l2_compat', - timeout : 60) +if asan_enabled + v4l2_compat_args += ['-s', asan_runtime] endif + +v4l2_compat_args += [v4l2_compat] + +test('v4l2_compat_test', v4l2_compat_test, + args : v4l2_compat_args, + suite : 'v4l2_compat', + timeout : 60) diff --git a/test/v4l2_compat/v4l2_compat_test.py b/test/v4l2_compat/v4l2_compat_test.py index a77585fc..443babc2 100755 --- a/test/v4l2_compat/v4l2_compat_test.py +++ b/test/v4l2_compat/v4l2_compat_test.py @@ -4,7 +4,7 @@ # # Author: Paul Elder <paul.elder@ideasonboard.com> # -# v4l2_compat_test.py - Test the V4L2 compatibility layer +# Test the V4L2 compatibility layer import argparse import glob @@ -57,8 +57,8 @@ def extract_result(result): return ret -def test_v4l2_compliance(v4l2_compliance, v4l2_compat, device, base_driver): - ret, output = run_with_stdout(v4l2_compliance, '-s', '-d', device, env={'LD_PRELOAD': v4l2_compat}) +def test_v4l2_compliance(v4l2_compliance, ld_preload, device, base_driver): + ret, output = run_with_stdout(v4l2_compliance, '-s', '-d', device, env={'LD_PRELOAD': ld_preload}) if ret < 0: output.append(f'Test for {device} terminated due to signal {signal.Signals(-ret).name}') return TestFail, output @@ -82,13 +82,21 @@ def main(argv): parser = argparse.ArgumentParser() parser.add_argument('-a', '--all', action='store_true', help='Test all available cameras') + parser.add_argument('-s', '--sanitizer', type=str, + help='Path to the address sanitizer (ASan) runtime') parser.add_argument('-v', '--verbose', action='store_true', help='Make the output verbose') parser.add_argument('v4l2_compat', type=str, help='Path to v4l2-compat.so') args = parser.parse_args(argv[1:]) - v4l2_compat = args.v4l2_compat + # Compute the LD_PRELOAD value by first loading ASan (if specified) and + # then the V4L2 compat layer. + ld_preload = [] + if args.sanitizer: + ld_preload.append(args.sanitizer) + ld_preload.append(args.v4l2_compat) + ld_preload = ':'.join(ld_preload) v4l2_compliance = shutil.which('v4l2-compliance') if v4l2_compliance is None: @@ -118,7 +126,7 @@ def main(argv): failed = [] drivers_tested = {} for device in dev_nodes: - ret, out = run_with_stdout(v4l2_ctl, '-D', '-d', device, env={'LD_PRELOAD': v4l2_compat}) + ret, out = run_with_stdout(v4l2_ctl, '-D', '-d', device, env={'LD_PRELOAD': ld_preload}) if ret < 0: failed.append(device) print(f'v4l2-ctl failed on {device} with v4l2-compat') @@ -144,7 +152,7 @@ def main(argv): continue print(f'Testing {device} with {driver} driver... ', end='') - ret, msg = test_v4l2_compliance(v4l2_compliance, v4l2_compat, device, driver) + ret, msg = test_v4l2_compliance(v4l2_compliance, ld_preload, device, driver) if ret == TestFail: failed.append(device) print('failed') diff --git a/test/v4l2_subdevice/meson.build b/test/v4l2_subdevice/meson.build index d82be3c6..277f29bb 100644 --- a/test/v4l2_subdevice/meson.build +++ b/test/v4l2_subdevice/meson.build @@ -1,14 +1,14 @@ # SPDX-License-Identifier: CC0-1.0 v4l2_subdevice_tests = [ - ['list_formats', 'list_formats.cpp'], - ['test_formats', 'test_formats.cpp'], + {'name': 'list_formats', 'sources': ['list_formats.cpp']}, + {'name': 'test_formats', 'sources': ['test_formats.cpp']}, ] -foreach t : v4l2_subdevice_tests - exe = executable(t[0], [t[1], 'v4l2_subdevice_test.cpp'], +foreach test : v4l2_subdevice_tests + exe = executable(test['name'], test['sources'], 'v4l2_subdevice_test.cpp', dependencies : libcamera_private, link_with : test_libraries, include_directories : test_includes_internal) - test(t[0], exe, suite : 'v4l2_subdevice', is_parallel : false) + test(test['name'], exe, suite : 'v4l2_subdevice', is_parallel : false) endforeach diff --git a/test/v4l2_subdevice/v4l2_subdevice_test.cpp b/test/v4l2_subdevice/v4l2_subdevice_test.cpp index d8fbfd9f..c349c9e3 100644 --- a/test/v4l2_subdevice/v4l2_subdevice_test.cpp +++ b/test/v4l2_subdevice/v4l2_subdevice_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * v4l2_subdevice_test.cpp - VIMC-based V4L2 subdevice test + * VIMC-based V4L2 subdevice test */ #include <iostream> diff --git a/test/v4l2_subdevice/v4l2_subdevice_test.h b/test/v4l2_subdevice/v4l2_subdevice_test.h index e73c583b..89b78302 100644 --- a/test/v4l2_subdevice/v4l2_subdevice_test.h +++ b/test/v4l2_subdevice/v4l2_subdevice_test.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * v4l2_subdevice_test.h - VIMC-based V4L2 subdevice test + * VIMC-based V4L2 subdevice test */ #pragma once diff --git a/test/v4l2_videodevice/controls.cpp b/test/v4l2_videodevice/controls.cpp index 0f603b85..b0130295 100644 --- a/test/v4l2_videodevice/controls.cpp +++ b/test/v4l2_videodevice/controls.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * controls.cpp - V4L2 device controls handling test + * V4L2 device controls handling test */ #include <algorithm> diff --git a/test/v4l2_videodevice/meson.build b/test/v4l2_videodevice/meson.build index 7a26f53d..87ea4f96 100644 --- a/test/v4l2_videodevice/meson.build +++ b/test/v4l2_videodevice/meson.build @@ -3,22 +3,22 @@ # Tests are listed in order of complexity. # They are not alphabetically sorted. v4l2_videodevice_tests = [ - ['double_open', 'double_open.cpp'], - ['controls', 'controls.cpp'], - ['formats', 'formats.cpp'], - ['dequeue_watchdog', 'dequeue_watchdog.cpp'], - ['request_buffers', 'request_buffers.cpp'], - ['buffer_cache', 'buffer_cache.cpp'], - ['stream_on_off', 'stream_on_off.cpp'], - ['capture_async', 'capture_async.cpp'], - ['buffer_sharing', 'buffer_sharing.cpp'], - ['v4l2_m2mdevice', 'v4l2_m2mdevice.cpp'], + {'name': 'double_open', 'sources': ['double_open.cpp']}, + {'name': 'controls', 'sources': ['controls.cpp']}, + {'name': 'formats', 'sources': ['formats.cpp']}, + {'name': 'dequeue_watchdog', 'sources': ['dequeue_watchdog.cpp']}, + {'name': 'request_buffers', 'sources': ['request_buffers.cpp']}, + {'name': 'buffer_cache', 'sources': ['buffer_cache.cpp']}, + {'name': 'stream_on_off', 'sources': ['stream_on_off.cpp']}, + {'name': 'capture_async', 'sources': ['capture_async.cpp']}, + {'name': 'buffer_sharing', 'sources': ['buffer_sharing.cpp']}, + {'name': 'v4l2_m2mdevice', 'sources': ['v4l2_m2mdevice.cpp']}, ] -foreach t : v4l2_videodevice_tests - exe = executable(t[0], [t[1], 'v4l2_videodevice_test.cpp'], +foreach test : v4l2_videodevice_tests + exe = executable(test['name'], [test['sources'], 'v4l2_videodevice_test.cpp'], dependencies : libcamera_private, link_with : test_libraries, include_directories : test_includes_internal) - test(t[0], exe, suite : 'v4l2_videodevice', is_parallel : false) + test(test['name'], exe, suite : 'v4l2_videodevice', is_parallel : false) endforeach diff --git a/test/v4l2_videodevice/v4l2_m2mdevice.cpp b/test/v4l2_videodevice/v4l2_m2mdevice.cpp index 852b853f..c45f581a 100644 --- a/test/v4l2_videodevice/v4l2_m2mdevice.cpp +++ b/test/v4l2_videodevice/v4l2_m2mdevice.cpp @@ -95,6 +95,11 @@ protected: V4L2VideoDevice *capture = vim2m_->capture(); V4L2VideoDevice *output = vim2m_->output(); + if (capture->controls().empty() || output->controls().empty()) { + cerr << "VIM2M device has no control" << endl; + return TestFail; + } + V4L2DeviceFormat format = {}; if (capture->getFormat(&format)) { cerr << "Failed to get capture format" << endl; diff --git a/test/v4l2_videodevice/v4l2_videodevice_test.cpp b/test/v4l2_videodevice/v4l2_videodevice_test.cpp index 125aafd6..1113cf5b 100644 --- a/test/v4l2_videodevice/v4l2_videodevice_test.cpp +++ b/test/v4l2_videodevice/v4l2_videodevice_test.cpp @@ -75,7 +75,7 @@ int V4L2VideoDeviceTest::init() format.fourcc = V4L2PixelFormat(V4L2_PIX_FMT_SBGGR8); V4L2SubdeviceFormat subformat = {}; - subformat.mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8; + subformat.code = MEDIA_BUS_FMT_SBGGR8_1X8; subformat.size = format.size; if (sensor_->setFormat(&subformat)) diff --git a/test/v4l2_videodevice/v4l2_videodevice_test.h b/test/v4l2_videodevice/v4l2_videodevice_test.h index d2de1a6d..b5871ce6 100644 --- a/test/v4l2_videodevice/v4l2_videodevice_test.h +++ b/test/v4l2_videodevice/v4l2_videodevice_test.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2018, Google Inc. * - * vl42device_test.h - libcamera v4l2device test base class + * libcamera v4l2device test base class */ #pragma once diff --git a/test/yaml-parser.cpp b/test/yaml-parser.cpp index 38f84823..81c82983 100644 --- a/test/yaml-parser.cpp +++ b/test/yaml-parser.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Google Inc. * - * yaml-parser.cpp - YAML parser operations tests + * YAML parser operations tests */ #include <array> @@ -24,16 +24,20 @@ using namespace std; static const string testYaml = "string: libcamera\n" "double: 3.14159\n" - "uint32_t: 100\n" - "int32_t: -100\n" + "int8_t: -100\n" + "uint8_t: 100\n" + "int16_t: -1000\n" + "uint16_t: 1000\n" + "int32_t: -100000\n" + "uint32_t: 100000\n" "size: [1920, 1080]\n" "list:\n" " - James\n" " - Mary\n" "dictionary:\n" " a: 1\n" - " b: 2\n" " c: 3\n" + " b: 2\n" "level1:\n" " level2:\n" " - [1, 2]\n" @@ -72,309 +76,359 @@ protected: return TestPass; } - int run() + enum class Type { + String, + Int8, + UInt8, + Int16, + UInt16, + Int32, + UInt32, + Double, + Size, + List, + Dictionary, + }; + + int testObjectType(const YamlObject &obj, const char *name, Type type) { - /* Test invalid YAML file */ - File file{ invalidYamlFile_ }; - if (!file.open(File::OpenModeFlag::ReadOnly)) { - cerr << "Fail to open invalid YAML file" << std::endl; - return TestFail; - } + bool isList = type == Type::List || type == Type::Size; + bool isScalar = !isList && type != Type::Dictionary; + bool isInteger8 = type == Type::Int8 || type == Type::UInt8; + bool isInteger16 = type == Type::Int16 || type == Type::UInt16; + bool isInteger32 = type == Type::Int32 || type == Type::UInt32; + bool isIntegerUpTo16 = isInteger8 || isInteger16; + bool isIntegerUpTo32 = isIntegerUpTo16 || isInteger32; + bool isSigned = type == Type::Int8 || type == Type::Int16 || + type == Type::Int32; - std::unique_ptr<YamlObject> root = YamlParser::parse(file); - if (root) { - cerr << "Invalid YAML file parse successfully" << std::endl; + if ((isScalar && !obj.isValue()) || (!isScalar && obj.isValue())) { + std::cerr + << "Object " << name << " type mismatch when compared to " + << "value" << std::endl; return TestFail; } - /* Test YAML file */ - file.close(); - file.setFileName(testYamlFile_); - if (!file.open(File::OpenModeFlag::ReadOnly)) { - cerr << "Fail to open test YAML file" << std::endl; + if ((isList && !obj.isList()) || (!isList && obj.isList())) { + std::cerr + << "Object " << name << " type mismatch when compared to " + << "list" << std::endl; return TestFail; } - root = YamlParser::parse(file); - - if (!root) { - cerr << "Fail to parse test YAML file: " << std::endl; + if ((type == Type::Dictionary && !obj.isDictionary()) || + (type != Type::Dictionary && obj.isDictionary())) { + std::cerr + << "Object " << name << " type mismatch when compared to " + << "dictionary" << std::endl; return TestFail; } - if (!root->isDictionary()) { - cerr << "YAML root is not dictionary" << std::endl; + if (!isScalar && obj.get<std::string>()) { + std::cerr + << "Object " << name << " didn't fail to parse as " + << "string" << std::endl; return TestFail; } - if (!root->contains("string")) { - cerr << "Missing string object in YAML root" << std::endl; + if (!isInteger8 && obj.get<int8_t>()) { + std::cerr + << "Object " << name << " didn't fail to parse as " + << "int8_t" << std::endl; return TestFail; } - if (!root->contains("double")) { - cerr << "Missing double object in YAML root" << std::endl; + if ((!isInteger8 || isSigned) && obj.get<uint8_t>()) { + std::cerr + << "Object " << name << " didn't fail to parse as " + << "uint8_t" << std::endl; return TestFail; } - if (!root->contains("int32_t")) { - cerr << "Missing int32_t object in YAML root" << std::endl; + if (!isIntegerUpTo16 && obj.get<int16_t>()) { + std::cerr + << "Object " << name << " didn't fail to parse as " + << "int16_t" << std::endl; return TestFail; } - if (!root->contains("uint32_t")) { - cerr << "Missing uint32_t object in YAML root" << std::endl; + if ((!isIntegerUpTo16 || isSigned) && obj.get<uint16_t>()) { + std::cerr + << "Object " << name << " didn't fail to parse as " + << "uint16_t" << std::endl; return TestFail; } - if (!root->contains("size")) { - cerr << "Missing Size object in YAML root" << std::endl; + if (!isIntegerUpTo32 && obj.get<int32_t>()) { + std::cerr + << "Object " << name << " didn't fail to parse as " + << "int32_t" << std::endl; return TestFail; } - if (!root->contains("list")) { - cerr << "Missing list object in YAML root" << std::endl; + if ((!isIntegerUpTo32 || isSigned) && obj.get<uint32_t>()) { + std::cerr + << "Object " << name << " didn't fail to parse as " + << "uint32_t" << std::endl; return TestFail; } - if (!root->contains("dictionary")) { - cerr << "Missing dictionary object in YAML root" << std::endl; + if (!isIntegerUpTo32 && type != Type::Double && obj.get<double>()) { + std::cerr + << "Object " << name << " didn't fail to parse as " + << "double" << std::endl; return TestFail; } - if (!root->contains("level1")) { - cerr << "Missing leveled object in YAML root" << std::endl; + if (type != Type::Size && obj.get<Size>()) { + std::cerr + << "Object " << name << " didn't fail to parse as " + << "Size" << std::endl; return TestFail; } - /* Test string object */ - bool ok; - auto &strObj = (*root)["string"]; + return TestPass; + } - if (strObj.isDictionary()) { - cerr << "String object parse as Dictionary" << std::endl; - return TestFail; - } + int testIntegerObject(const YamlObject &obj, const char *name, Type type, + int64_t value) + { + uint64_t unsignedValue = static_cast<uint64_t>(value); + std::string strValue = std::to_string(value); + bool isInteger8 = type == Type::Int8 || type == Type::UInt8; + bool isInteger16 = type == Type::Int16 || type == Type::UInt16; + bool isSigned = type == Type::Int8 || type == Type::Int16 || + type == Type::Int32; - if (strObj.isList()) { - cerr << "String object parse as List" << std::endl; - return TestFail; - } + /* All integers can be parsed as strings or double. */ - if (strObj.get<string>("", &ok) != "libcamera" || !ok) { - cerr << "String object parse as wrong content" << std::endl; + if (obj.get<string>().value_or("") != strValue || + obj.get<string>("") != strValue) { + std::cerr + << "Object " << name << " failed to parse as " + << "string" << std::endl; return TestFail; } - if (strObj.get<int32_t>(-1, &ok) != -1 || ok) { - cerr << "String object parse as integer" << std::endl; + if (obj.get<double>().value_or(0.0) != value || + obj.get<double>(0.0) != value) { + std::cerr + << "Object " << name << " failed to parse as " + << "double" << std::endl; return TestFail; } - if (strObj.get<uint32_t>(1, &ok) != 1 || ok) { - cerr << "String object parse as unsigned integer" << std::endl; - return TestFail; + if (isInteger8) { + if (obj.get<int8_t>().value_or(0) != value || + obj.get<int8_t>(0) != value) { + std::cerr + << "Object " << name << " failed to parse as " + << "int8_t" << std::endl; + return TestFail; + } } - if (strObj.get<double>(1.0, &ok) != 1.0 || ok) { - cerr << "String object parse as double" << std::endl; - return TestFail; + if (isInteger8 && !isSigned) { + if (obj.get<uint8_t>().value_or(0) != unsignedValue || + obj.get<uint8_t>(0) != unsignedValue) { + std::cerr + << "Object " << name << " failed to parse as " + << "uint8_t" << std::endl; + return TestFail; + } } - if (strObj.get<Size>(Size(0, 0), &ok) != Size(0, 0) || ok) { - cerr << "String object parse as Size" << std::endl; - return TestFail; + if (isInteger8 || isInteger16) { + if (obj.get<int16_t>().value_or(0) != value || + obj.get<int16_t>(0) != value) { + std::cerr + << "Object " << name << " failed to parse as " + << "int16_t" << std::endl; + return TestFail; + } } - /* Test int32_t object */ - auto &int32Obj = (*root)["int32_t"]; - - if (int32Obj.isDictionary()) { - cerr << "Integer object parse as Dictionary" << std::endl; - return TestFail; + if ((isInteger8 || isInteger16) && !isSigned) { + if (obj.get<uint16_t>().value_or(0) != unsignedValue || + obj.get<uint16_t>(0) != unsignedValue) { + std::cerr + << "Object " << name << " failed to parse as " + << "uint16_t" << std::endl; + return TestFail; + } } - if (int32Obj.isList()) { - cerr << "Integer object parse as Integer" << std::endl; + if (obj.get<int32_t>().value_or(0) != value || + obj.get<int32_t>(0) != value) { + std::cerr + << "Object " << name << " failed to parse as " + << "int32_t" << std::endl; return TestFail; } - if (int32Obj.get<int32_t>(-100, &ok) != -100 || !ok) { - cerr << "Integer object parse as wrong value" << std::endl; - return TestFail; + if (!isSigned) { + if (obj.get<uint32_t>().value_or(0) != unsignedValue || + obj.get<uint32_t>(0) != unsignedValue) { + std::cerr + << "Object " << name << " failed to parse as " + << "uint32_t" << std::endl; + return TestFail; + } } - if (int32Obj.get<string>("", &ok) != "-100" || !ok) { - cerr << "Integer object fail to parse as string" << std::endl; - return TestFail; - } + return TestPass; + } - if (int32Obj.get<double>(1.0, &ok) != -100.0 || !ok) { - cerr << "Integer object fail to parse as double" << std::endl; + int run() + { + /* Test invalid YAML file */ + File file{ invalidYamlFile_ }; + if (!file.open(File::OpenModeFlag::ReadOnly)) { + cerr << "Fail to open invalid YAML file" << std::endl; return TestFail; } - if (int32Obj.get<uint32_t>(1, &ok) != 1 || ok) { - cerr << "Negative integer object parse as unsigned integer" << std::endl; + std::unique_ptr<YamlObject> root = YamlParser::parse(file); + if (root) { + cerr << "Invalid YAML file parse successfully" << std::endl; return TestFail; } - if (int32Obj.get<Size>(Size(0, 0), &ok) != Size(0, 0) || ok) { - cerr << "Integer object parse as Size" << std::endl; + /* Test YAML file */ + file.close(); + file.setFileName(testYamlFile_); + if (!file.open(File::OpenModeFlag::ReadOnly)) { + cerr << "Fail to open test YAML file" << std::endl; return TestFail; } - /* Test uint32_t object */ - auto &uint32Obj = (*root)["uint32_t"]; + root = YamlParser::parse(file); - if (uint32Obj.isDictionary()) { - cerr << "Unsigned integer object parse as Dictionary" << std::endl; + if (!root) { + cerr << "Fail to parse test YAML file: " << std::endl; return TestFail; } - if (uint32Obj.isList()) { - cerr << "Unsigned integer object parse as List" << std::endl; + if (!root->isDictionary()) { + cerr << "YAML root is not dictionary" << std::endl; return TestFail; } - if (uint32Obj.get<int32_t>(-1, &ok) != 100 || !ok) { - cerr << "Unsigned integer object fail to parse as integer" << std::endl; - return TestFail; - } + std::vector<const char *> rootElemNames = { + "string", "double", "int8_t", "uint8_t", "int16_t", + "uint16_t", "int32_t", "uint32_t", "size", "list", + "dictionary", "level1", + }; - if (uint32Obj.get<string>("", &ok) != "100" || !ok) { - cerr << "Unsigned integer object fail to parse as string" << std::endl; - return TestFail; + for (const char *name : rootElemNames) { + if (!root->contains(name)) { + cerr << "Missing " << name << " object in YAML root" + << std::endl; + return TestFail; + } } - if (uint32Obj.get<double>(1.0, &ok) != 100.0 || !ok) { - cerr << "Unsigned integer object fail to parse as double" << std::endl; - return TestFail; - } + /* Test string object */ + auto &strObj = (*root)["string"]; - if (uint32Obj.get<uint32_t>(100, &ok) != 100 || !ok) { - cerr << "Unsigned integer object parsed as wrong value" << std::endl; + if (testObjectType(strObj, "string", Type::String) != TestPass) return TestFail; - } - if (uint32Obj.get<Size>(Size(0, 0), &ok) != Size(0, 0) || ok) { - cerr << "Unsigned integer object parsed as Size" << std::endl; + if (strObj.get<string>().value_or("") != "libcamera" || + strObj.get<string>("") != "libcamera") { + cerr << "String object parse as wrong content" << std::endl; return TestFail; } - /* Test double value */ - auto &doubleObj = (*root)["double"]; + /* Test int8_t object */ + auto &int8Obj = (*root)["int8_t"]; - if (doubleObj.isDictionary()) { - cerr << "Double object parse as Dictionary" << std::endl; + if (testObjectType(int8Obj, "int8_t", Type::Int8) != TestPass) return TestFail; - } - if (doubleObj.isList()) { - cerr << "Double object parse as List" << std::endl; + if (testIntegerObject(int8Obj, "int8_t", Type::Int8, -100) != TestPass) return TestFail; - } - if (doubleObj.get<string>("", &ok) != "3.14159" || !ok) { - cerr << "Double object fail to parse as string" << std::endl; - return TestFail; - } + /* Test uint8_t object */ + auto &uint8Obj = (*root)["uint8_t"]; - if (doubleObj.get<double>(1.0, &ok) != 3.14159 || !ok) { - cerr << "Double object parse as wrong value" << std::endl; + if (testObjectType(uint8Obj, "uint8_t", Type::UInt8) != TestPass) return TestFail; - } - if (doubleObj.get<int32_t>(-1, &ok) != -1 || ok) { - cerr << "Double object parse as integer" << std::endl; + if (testIntegerObject(uint8Obj, "uint8_t", Type::UInt8, 100) != TestPass) return TestFail; - } - if (doubleObj.get<uint32_t>(1, &ok) != 1 || ok) { - cerr << "Double object parse as unsigned integer" << std::endl; + /* Test int16_t object */ + auto &int16Obj = (*root)["int16_t"]; + + if (testObjectType(int16Obj, "int16_t", Type::Int16) != TestPass) return TestFail; - } - if (doubleObj.get<Size>(Size(0, 0), &ok) != Size(0, 0) || ok) { - cerr << "Double object parse as Size" << std::endl; + if (testIntegerObject(int16Obj, "int16_t", Type::Int16, -1000) != TestPass) return TestFail; - } - /* Test Size value */ - auto &sizeObj = (*root)["size"]; + /* Test uint16_t object */ + auto &uint16Obj = (*root)["uint16_t"]; - if (sizeObj.isDictionary()) { - cerr << "Size object parse as Dictionary" << std::endl; + if (testObjectType(uint16Obj, "uint16_t", Type::UInt16) != TestPass) return TestFail; - } - if (!sizeObj.isList()) { - cerr << "Size object parse as List" << std::endl; + if (testIntegerObject(uint16Obj, "uint16_t", Type::UInt16, 1000) != TestPass) return TestFail; - } - if (sizeObj.get<string>("", &ok) != "" || ok) { - cerr << "Size object parse as string" << std::endl; - return TestFail; - } + /* Test int32_t object */ + auto &int32Obj = (*root)["int32_t"]; - if (sizeObj.get<double>(1.0, &ok) != 1.0 || ok) { - cerr << "Size object parse as double" << std::endl; + if (testObjectType(int32Obj, "int32_t", Type::Int32) != TestPass) return TestFail; - } - if (sizeObj.get<int32_t>(-1, &ok) != -1 || ok) { - cerr << "Size object parse as integer" << std::endl; + if (testIntegerObject(int32Obj, "int32_t", Type::Int32, -100000) != TestPass) return TestFail; - } - if (sizeObj.get<uint32_t>(1, &ok) != 1 || ok) { - cerr << "Size object parse as unsigned integer" << std::endl; + /* Test uint32_t object */ + auto &uint32Obj = (*root)["uint32_t"]; + + if (testObjectType(uint32Obj, "uint32_t", Type::UInt32) != TestPass) return TestFail; - } - if (sizeObj.get<Size>(Size(0, 0), &ok) != Size(1920, 1080) || !ok) { - cerr << "Size object parse as wrong value" << std::endl; + if (testIntegerObject(uint32Obj, "uint32_t", Type::UInt32, 100000) != TestPass) return TestFail; - } - /* Test list object */ - auto &listObj = (*root)["list"]; + /* Test double value */ + auto &doubleObj = (*root)["double"]; - if (listObj.isDictionary()) { - cerr << "List object parse as Dictionary" << std::endl; + if (testObjectType(doubleObj, "double", Type::Double) != TestPass) return TestFail; - } - if (!listObj.isList()) { - cerr << "List object fail to parse as List" << std::endl; + if (doubleObj.get<string>().value_or("") != "3.14159" || + doubleObj.get<string>("") != "3.14159") { + cerr << "Double object fail to parse as string" << std::endl; return TestFail; } - if (listObj.get<string>("", &ok) != "" || ok) { - cerr << "List object parse as string" << std::endl; + if (doubleObj.get<double>().value_or(0.0) != 3.14159 || + doubleObj.get<double>(0.0) != 3.14159) { + cerr << "Double object parse as wrong value" << std::endl; return TestFail; } - if (listObj.get<double>(1.0, &ok) != 1.0 || ok) { - cerr << "List object parse as double" << std::endl; - return TestFail; - } + /* Test Size value */ + auto &sizeObj = (*root)["size"]; - if (listObj.get<int32_t>(-1, &ok) != -1 || ok) { - cerr << "List object parse as integer" << std::endl; + if (testObjectType(sizeObj, "size", Type::Size) != TestPass) return TestFail; - } - if (listObj.get<uint32_t>(1, &ok) != 1 || ok) { - cerr << "List object parse as unsigne integer" << std::endl; + if (sizeObj.get<Size>().value_or(Size(0, 0)) != Size(1920, 1080) || + sizeObj.get<Size>(Size(0, 0)) != Size(1920, 1080)) { + cerr << "Size object parse as wrong value" << std::endl; return TestFail; } - if (listObj.get<Size>(Size(0, 0), &ok) != Size(0, 0) || ok) { - cerr << "String list object parse as Size" << std::endl; + /* Test list object */ + auto &listObj = (*root)["list"]; + + if (testObjectType(listObj, "list", Type::List) != TestPass) return TestFail; - } static constexpr std::array<const char *, 2> listValues{ "James", @@ -414,45 +468,13 @@ protected: /* Test dictionary object */ auto &dictObj = (*root)["dictionary"]; - if (!dictObj.isDictionary()) { - cerr << "Dictionary object fail to parse as Dictionary" << std::endl; + if (testObjectType(dictObj, "dictionary", Type::Dictionary) != TestPass) return TestFail; - } - if (dictObj.isList()) { - cerr << "Dictionary object parse as List" << std::endl; - return TestFail; - } - - if (dictObj.get<string>("", &ok) != "" || ok) { - cerr << "Dictionary object parse as string" << std::endl; - return TestFail; - } - - if (dictObj.get<double>(1.0, &ok) != 1.0 || ok) { - cerr << "Dictionary object parse as double" << std::endl; - return TestFail; - } - - if (dictObj.get<int32_t>(-1, &ok) != -1 || ok) { - cerr << "Dictionary object parse as integer" << std::endl; - return TestFail; - } - - if (dictObj.get<uint32_t>(1, &ok) != 1 || ok) { - cerr << "Dictionary object parse as unsigned integer" << std::endl; - return TestFail; - } - - if (dictObj.get<Size>(Size(0, 0), &ok) != Size(0, 0) || ok) { - cerr << "Dictionary object parse as Size" << std::endl; - return TestFail; - } - - std::map<std::string, int> dictValues{ { + static constexpr std::array<std::pair<const char *, int>, 3> dictValues{ { { "a", 1 }, - { "b", 2 }, { "c", 3 }, + { "b", 2 }, } }; size_t dictSize = dictValues.size(); @@ -470,8 +492,8 @@ protected: return TestFail; } - const auto item = dictValues.find(key); - if (item == dictValues.end()) { + const auto &item = dictValues[i]; + if (item.first != key) { std::cerr << "Dictionary key " << i << " has wrong value" << std::endl; return TestFail; @@ -483,17 +505,12 @@ protected: return TestFail; } - if (elem.get<int32_t>(0) != item->second) { + if (elem.get<int32_t>(0) != item.second) { std::cerr << "Dictionary element " << i << " has wrong value" << std::endl; return TestFail; } - /* - * Erase the item to make sure that each iteration - * produces a different value. - */ - dictValues.erase(item); i++; } @@ -524,6 +541,12 @@ protected: return TestFail; } + const auto &values = firstElement.getList<uint16_t>(); + if (!values || values->size() != 2 || (*values)[0] != 1 || (*values)[1] != 2) { + cerr << "getList() failed to return correct vector" << std::endl; + return TestFail; + } + auto &secondElement = level2Obj[1]; if (!secondElement.isDictionary() || !secondElement.contains("one") || |