[libcamera-devel,04/10] test: Add test for the Fence class
diff mbox series

Message ID 20211028111520.256612-5-jacopo@jmondi.org
State Superseded
Headers show
Series
  • libcamera: Introduce Fence support
Related show

Commit Message

Jacopo Mondi Oct. 28, 2021, 11:15 a.m. UTC
Add a test for the Fence class by testing completion and expiration of
a Fence and testing that move semantic is implemented correctly.

Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
---
 test/fence.cpp   | 148 +++++++++++++++++++++++++++++++++++++++++++++++
 test/meson.build |   1 +
 2 files changed, 149 insertions(+)
 create mode 100644 test/fence.cpp

Comments

Kieran Bingham Nov. 9, 2021, 1:12 p.m. UTC | #1
Quoting Jacopo Mondi (2021-10-28 12:15:14)
> Add a test for the Fence class by testing completion and expiration of
> a Fence and testing that move semantic is implemented correctly.
> 
> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> ---
>  test/fence.cpp   | 148 +++++++++++++++++++++++++++++++++++++++++++++++
>  test/meson.build |   1 +
>  2 files changed, 149 insertions(+)
>  create mode 100644 test/fence.cpp
> 
> diff --git a/test/fence.cpp b/test/fence.cpp
> new file mode 100644
> index 000000000000..9fb92b1c250b
> --- /dev/null
> +++ b/test/fence.cpp
> @@ -0,0 +1,148 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2019, Google Inc.

2021 perhaps.

> + *
> + * fence.cpp - Fence test
> + */
> +
> +#include <iostream>
> +#include <sys/eventfd.h>
> +#include <unistd.h>
> +
> +#include <libcamera/base/event_dispatcher_poll.h>
> +#include <libcamera/base/thread.h>
> +#include <libcamera/base/timer.h>
> +#include <libcamera/internal/fence.h>

Definitely something wrong with checkstyle missing these:

$ clang-format-diff-file test/fence.cpp
--- test/fence.cpp
+++ test/fence.cpp.clang
@@ -12,6 +12,7 @@
 #include <libcamera/base/event_dispatcher_poll.h>
 #include <libcamera/base/thread.h>
 #include <libcamera/base/timer.h>
+
 #include <libcamera/internal/fence.h>

 #include "test.h"

> +
> +#include "test.h"
> +
> +using namespace libcamera;
> +using namespace std;
> +
> +class FenceTest : public Test
> +{
> +protected:
> +       int init();
> +       int run();
> +       void cleanup();
> +
> +private:
> +       void timeout();
> +       void completed();
> +
> +       int eventfd_;
> +       Timer timer_;
> +       EventDispatcher *dispatcher;
> +       Fence *fence_;
> +};
> +
> +int FenceTest::init()
> +{
> +       timer_.timeout.connect(this, &FenceTest::timeout);
> +       dispatcher = Thread::current()->eventDispatcher();
> +
> +       eventfd_ = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
> +       if (eventfd_ < 0) {
> +               cerr << "Unable to create eventfd" << endl;
> +               return TestFail;
> +       }
> +
> +       fence_ = new Fence(eventfd_);
> +       if (!eventfd_) {
> +               cerr << "Fence creation should not reset the file descriptor" << endl;
> +               return TestFail;
> +       }
> +
> +       if (fence_->fd() != eventfd_) {
> +               cerr << "Fence file descriptor should not be duplicated" << endl;

Why not? What happens if someone closes the underlying eventfd? Can we
have a copy so that ours stays with us until we're done with it? Or does
that break some semantics somewhere...?

> +               return TestFail;
> +       }
> +
> +       fence_->complete.connect(this, &FenceTest::completed);
> +
> +       return 0;
> +}
> +

aha, the timeout() below is a helper to 'activate' the fence. Can that
be highlighted with a banner comment for the function? It was really
easy to miss - as it sounded like a timeout event handler - not an event
'generator'.


> +void FenceTest::timeout()
> +{
> +       uint64_t value = 1;
> +       ssize_t ret = write(eventfd_, &value, sizeof(value));
> +       if (ret != sizeof(value))
> +               cerr << "Failed to write to event fd" << endl;
> +
> +       /* Let the fence complete on the eventfd read event. */
> +       dispatcher->processEvents();
> +}
> +

Same here...

> +void FenceTest::completed()
> +{
> +       if (fence_->expired())
> +               return;
> +
> +       uint64_t value;
> +       ssize_t ret = read(eventfd_, &value, sizeof(value));
> +       if (ret != sizeof(value))
> +               cerr << "Failed to read from event fd" << endl;
> +}
> +
> +int FenceTest::run()
> +{
> +       /* Activate the fence and schedule a wake-up before it expires. */
> +       fence_->enable();
> +       timer_.start(50);

Does this activate the underlying eventfd somehow?
(Edit, yes, I see it now - but it wasn't obvious to start with).

> +
> +       dispatcher->processEvents();
> +
> +       if (fence_->expired()) {
> +               cerr << "Fence should not have expired" << endl;
> +               return TestFail;
> +       }
> +
> +       if (!fence_->completed()) {
> +               cerr << "Fence should have completed" << endl;
> +               return TestFail;
> +       }
> +
> +       /* Now let the fence expire. */
> +       fence_->enable();
> +       dispatcher->processEvents();
> +
> +       if (fence_->completed()) {
> +               cerr << "Fence should not have completed" << endl;
> +               return TestFail;
> +       }
> +
> +       if (!fence_->expired()) {
> +               cerr << "Fence should have expired" << endl;
> +               return TestFail;
> +       }
> +
> +       /*
> +        * Test fence move semantic.
> +        *
> +        * Create two temporary fences and verify we can move them.
> +        */
> +       Fence fence1(eventfd_, 500);
> +       Fence fence2(0, 500);
> +       fence2 = std::move(fence1);
> +
> +       if (fence1.fd() != -1) {
> +               cerr << "A moved fence should have an invalid fd" << endl;
> +               return TestFail;
> +       }
> +
> +       if (fence2.fd() != eventfd_ || fence2.timeout() != 500) {
> +               cerr << "Faile to move fence" << endl;
> +               return TestFail;
> +       }

What happens to the signals that are connected to fence1 and fence2. Is
there anything we can do to test those, and their expected interactions
after a move?


> +
> +       return 0;
> +}
> +
> +void FenceTest::cleanup()
> +{
> +       close(eventfd_);
> +       delete fence_;
> +}
> +
> +TEST_REGISTER(FenceTest)
> diff --git a/test/meson.build b/test/meson.build
> index d0466f17d7b6..377e392628bf 100644
> --- a/test/meson.build
> +++ b/test/meson.build
> @@ -40,6 +40,7 @@ internal_tests = [
>      ['event-dispatcher',                'event-dispatcher.cpp'],
>      ['event-thread',                    'event-thread.cpp'],
>      ['file',                            'file.cpp'],
> +    ['fence',                           'fence.cpp'],
>      ['file-descriptor',                 'file-descriptor.cpp'],
>      ['flags',                           'flags.cpp'],
>      ['hotplug-cameras',                 'hotplug-cameras.cpp'],
> -- 
> 2.33.1
>
Jacopo Mondi Nov. 9, 2021, 5:28 p.m. UTC | #2
Hi Kieran,

On Tue, Nov 09, 2021 at 01:12:34PM +0000, Kieran Bingham wrote:
> Quoting Jacopo Mondi (2021-10-28 12:15:14)
> > Add a test for the Fence class by testing completion and expiration of
> > a Fence and testing that move semantic is implemented correctly.
> >
> > Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> > ---
> >  test/fence.cpp   | 148 +++++++++++++++++++++++++++++++++++++++++++++++
> >  test/meson.build |   1 +
> >  2 files changed, 149 insertions(+)
> >  create mode 100644 test/fence.cpp
> >
> > diff --git a/test/fence.cpp b/test/fence.cpp
> > new file mode 100644
> > index 000000000000..9fb92b1c250b
> > --- /dev/null
> > +++ b/test/fence.cpp
> > @@ -0,0 +1,148 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * Copyright (C) 2019, Google Inc.
>
> 2021 perhaps.
>

Ups

> > + *
> > + * fence.cpp - Fence test
> > + */
> > +
> > +#include <iostream>
> > +#include <sys/eventfd.h>
> > +#include <unistd.h>
> > +
> > +#include <libcamera/base/event_dispatcher_poll.h>
> > +#include <libcamera/base/thread.h>
> > +#include <libcamera/base/timer.h>
> > +#include <libcamera/internal/fence.h>
>
> Definitely something wrong with checkstyle missing these:
>
> $ clang-format-diff-file test/fence.cpp
> --- test/fence.cpp
> +++ test/fence.cpp.clang
> @@ -12,6 +12,7 @@
>  #include <libcamera/base/event_dispatcher_poll.h>
>  #include <libcamera/base/thread.h>
>  #include <libcamera/base/timer.h>
> +
>  #include <libcamera/internal/fence.h>
>
>  #include "test.h"
>
> > +
> > +#include "test.h"
> > +
> > +using namespace libcamera;
> > +using namespace std;
> > +
> > +class FenceTest : public Test
> > +{
> > +protected:
> > +       int init();
> > +       int run();
> > +       void cleanup();
> > +
> > +private:
> > +       void timeout();
> > +       void completed();
> > +
> > +       int eventfd_;
> > +       Timer timer_;
> > +       EventDispatcher *dispatcher;
> > +       Fence *fence_;
> > +};
> > +
> > +int FenceTest::init()
> > +{
> > +       timer_.timeout.connect(this, &FenceTest::timeout);
> > +       dispatcher = Thread::current()->eventDispatcher();
> > +
> > +       eventfd_ = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
> > +       if (eventfd_ < 0) {
> > +               cerr << "Unable to create eventfd" << endl;
> > +               return TestFail;
> > +       }
> > +
> > +       fence_ = new Fence(eventfd_);
> > +       if (!eventfd_) {
> > +               cerr << "Fence creation should not reset the file descriptor" << endl;
> > +               return TestFail;
> > +       }
> > +
> > +       if (fence_->fd() != eventfd_) {
> > +               cerr << "Fence file descriptor should not be duplicated" << endl;
>
> Why not? What happens if someone closes the underlying eventfd? Can we
> have a copy so that ours stays with us until we're done with it? Or does
> that break some semantics somewhere...?
>

It would break the move-only semantic of the Fence class. Making sure
the underlying FileDescriptor is handled by a single component is the
key to make it easier to guarantee the Fence is either closed or has
to be handled by the application when returned from a completed
Request.

We could indeed make the FrameBuffer interface accept a &&FileDescriptor
to make sure application do not try to close it while it is being
handled by the library.

> > +               return TestFail;
> > +       }
> > +
> > +       fence_->complete.connect(this, &FenceTest::completed);
> > +
> > +       return 0;
> > +}
> > +
>
> aha, the timeout() below is a helper to 'activate' the fence. Can that
> be highlighted with a banner comment for the function? It was really
> easy to miss - as it sounded like a timeout event handler - not an event
> 'generator'.
>
>
> > +void FenceTest::timeout()
> > +{
> > +       uint64_t value = 1;
> > +       ssize_t ret = write(eventfd_, &value, sizeof(value));
> > +       if (ret != sizeof(value))
> > +               cerr << "Failed to write to event fd" << endl;
> > +
> > +       /* Let the fence complete on the eventfd read event. */
> > +       dispatcher->processEvents();
> > +}
> > +
>
> Same here...
>
> > +void FenceTest::completed()
> > +{
> > +       if (fence_->expired())
> > +               return;
> > +
> > +       uint64_t value;
> > +       ssize_t ret = read(eventfd_, &value, sizeof(value));
> > +       if (ret != sizeof(value))
> > +               cerr << "Failed to read from event fd" << endl;
> > +}
> > +
> > +int FenceTest::run()
> > +{
> > +       /* Activate the fence and schedule a wake-up before it expires. */
> > +       fence_->enable();
> > +       timer_.start(50);
>
> Does this activate the underlying eventfd somehow?
> (Edit, yes, I see it now - but it wasn't obvious to start with).
>
> > +
> > +       dispatcher->processEvents();
> > +
> > +       if (fence_->expired()) {
> > +               cerr << "Fence should not have expired" << endl;
> > +               return TestFail;
> > +       }
> > +
> > +       if (!fence_->completed()) {
> > +               cerr << "Fence should have completed" << endl;
> > +               return TestFail;
> > +       }
> > +
> > +       /* Now let the fence expire. */
> > +       fence_->enable();
> > +       dispatcher->processEvents();
> > +
> > +       if (fence_->completed()) {
> > +               cerr << "Fence should not have completed" << endl;
> > +               return TestFail;
> > +       }
> > +
> > +       if (!fence_->expired()) {
> > +               cerr << "Fence should have expired" << endl;
> > +               return TestFail;
> > +       }
> > +
> > +       /*
> > +        * Test fence move semantic.
> > +        *
> > +        * Create two temporary fences and verify we can move them.
> > +        */
> > +       Fence fence1(eventfd_, 500);
> > +       Fence fence2(0, 500);
> > +       fence2 = std::move(fence1);
> > +
> > +       if (fence1.fd() != -1) {
> > +               cerr << "A moved fence should have an invalid fd" << endl;
> > +               return TestFail;
> > +       }
> > +
> > +       if (fence2.fd() != eventfd_ || fence2.timeout() != 500) {
> > +               cerr << "Faile to move fence" << endl;
> > +               return TestFail;
> > +       }
>
> What happens to the signals that are connected to fence1 and fence2. Is
> there anything we can do to test those, and their expected interactions
> after a move?

As per the previous review comment, I should disconnect slots from a
moved fence.

Thanks
  j

>
>
> > +
> > +       return 0;
> > +}
> > +
> > +void FenceTest::cleanup()
> > +{
> > +       close(eventfd_);
> > +       delete fence_;
> > +}
> > +
> > +TEST_REGISTER(FenceTest)
> > diff --git a/test/meson.build b/test/meson.build
> > index d0466f17d7b6..377e392628bf 100644
> > --- a/test/meson.build
> > +++ b/test/meson.build
> > @@ -40,6 +40,7 @@ internal_tests = [
> >      ['event-dispatcher',                'event-dispatcher.cpp'],
> >      ['event-thread',                    'event-thread.cpp'],
> >      ['file',                            'file.cpp'],
> > +    ['fence',                           'fence.cpp'],
> >      ['file-descriptor',                 'file-descriptor.cpp'],
> >      ['flags',                           'flags.cpp'],
> >      ['hotplug-cameras',                 'hotplug-cameras.cpp'],
> > --
> > 2.33.1
> >

Patch
diff mbox series

diff --git a/test/fence.cpp b/test/fence.cpp
new file mode 100644
index 000000000000..9fb92b1c250b
--- /dev/null
+++ b/test/fence.cpp
@@ -0,0 +1,148 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * fence.cpp - Fence test
+ */
+
+#include <iostream>
+#include <sys/eventfd.h>
+#include <unistd.h>
+
+#include <libcamera/base/event_dispatcher_poll.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
+#include <libcamera/internal/fence.h>
+
+#include "test.h"
+
+using namespace libcamera;
+using namespace std;
+
+class FenceTest : public Test
+{
+protected:
+	int init();
+	int run();
+	void cleanup();
+
+private:
+	void timeout();
+	void completed();
+
+	int eventfd_;
+	Timer timer_;
+	EventDispatcher *dispatcher;
+	Fence *fence_;
+};
+
+int FenceTest::init()
+{
+	timer_.timeout.connect(this, &FenceTest::timeout);
+	dispatcher = Thread::current()->eventDispatcher();
+
+	eventfd_ = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
+	if (eventfd_ < 0) {
+		cerr << "Unable to create eventfd" << endl;
+		return TestFail;
+	}
+
+	fence_ = new Fence(eventfd_);
+	if (!eventfd_) {
+		cerr << "Fence creation should not reset the file descriptor" << endl;
+		return TestFail;
+	}
+
+	if (fence_->fd() != eventfd_) {
+		cerr << "Fence file descriptor should not be duplicated" << endl;
+		return TestFail;
+	}
+
+	fence_->complete.connect(this, &FenceTest::completed);
+
+	return 0;
+}
+
+void FenceTest::timeout()
+{
+	uint64_t value = 1;
+	ssize_t ret = write(eventfd_, &value, sizeof(value));
+	if (ret != sizeof(value))
+		cerr << "Failed to write to event fd" << endl;
+
+	/* Let the fence complete on the eventfd read event. */
+	dispatcher->processEvents();
+}
+
+void FenceTest::completed()
+{
+	if (fence_->expired())
+		return;
+
+	uint64_t value;
+	ssize_t ret = read(eventfd_, &value, sizeof(value));
+	if (ret != sizeof(value))
+		cerr << "Failed to read from event fd" << endl;
+}
+
+int FenceTest::run()
+{
+	/* Activate the fence and schedule a wake-up before it expires. */
+	fence_->enable();
+	timer_.start(50);
+
+	dispatcher->processEvents();
+
+	if (fence_->expired()) {
+		cerr << "Fence should not have expired" << endl;
+		return TestFail;
+	}
+
+	if (!fence_->completed()) {
+		cerr << "Fence should have completed" << endl;
+		return TestFail;
+	}
+
+	/* Now let the fence expire. */
+	fence_->enable();
+	dispatcher->processEvents();
+
+	if (fence_->completed()) {
+		cerr << "Fence should not have completed" << endl;
+		return TestFail;
+	}
+
+	if (!fence_->expired()) {
+		cerr << "Fence should have expired" << endl;
+		return TestFail;
+	}
+
+	/*
+	 * Test fence move semantic.
+	 *
+	 * Create two temporary fences and verify we can move them.
+	 */
+	Fence fence1(eventfd_, 500);
+	Fence fence2(0, 500);
+	fence2 = std::move(fence1);
+
+	if (fence1.fd() != -1) {
+		cerr << "A moved fence should have an invalid fd" << endl;
+		return TestFail;
+	}
+
+	if (fence2.fd() != eventfd_ || fence2.timeout() != 500) {
+		cerr << "Faile to move fence" << endl;
+		return TestFail;
+	}
+
+	return 0;
+}
+
+void FenceTest::cleanup()
+{
+	close(eventfd_);
+	delete fence_;
+}
+
+TEST_REGISTER(FenceTest)
diff --git a/test/meson.build b/test/meson.build
index d0466f17d7b6..377e392628bf 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -40,6 +40,7 @@  internal_tests = [
     ['event-dispatcher',                'event-dispatcher.cpp'],
     ['event-thread',                    'event-thread.cpp'],
     ['file',                            'file.cpp'],
+    ['fence',                           'fence.cpp'],
     ['file-descriptor',                 'file-descriptor.cpp'],
     ['flags',                           'flags.cpp'],
     ['hotplug-cameras',                 'hotplug-cameras.cpp'],