diff --git a/test/ipc/meson.build b/test/ipc/meson.build
new file mode 100644
index 0000000000000000..0a425d4e7241c753
--- /dev/null
+++ b/test/ipc/meson.build
@@ -0,0 +1,20 @@
+# Tests are listed in order of complexity.
+# They are not alphabetically sorted.
+ipc_tests = [
+    [ 'unixsocket',  'unixsocket.cpp', 'unixsocket-slave', 'unixsocket-slave.cpp' ],
+]
+
+foreach t : ipc_tests
+    exe = executable(t[0], t[1],
+                     dependencies : libcamera_dep,
+                     link_with : test_libraries,
+                     include_directories : test_includes_internal)
+
+    slave = executable(t[2], t[3],
+                     dependencies : libcamera_dep,
+                     include_directories : test_includes_internal)
+
+    test(t[0], exe, suite : 'ipc', is_parallel : false)
+endforeach
+
+config_h.set('IPC_TEST_DIR', '"' +  meson.current_build_dir() + '"')
diff --git a/test/ipc/unixsocket-slave.cpp b/test/ipc/unixsocket-slave.cpp
new file mode 100644
index 0000000000000000..ec27f6bf29823173
--- /dev/null
+++ b/test/ipc/unixsocket-slave.cpp
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * unixsocket-slave.cpp - Unix socket IPC slave runner
+ */
+
+#include "unixsocket.h"
+
+#include <algorithm>
+#include <iostream>
+#include <string.h>
+#include <unistd.h>
+
+#include "ipc_unixsocket.h"
+
+using namespace std;
+using namespace libcamera;
+
+int main(int argc, char **argv)
+{
+	if (argc != 2) {
+		cerr << "usage: %s <ipc fd>" << endl;
+		return EXIT_FAILURE;
+	}
+
+	int ipcfd = std::stoi(argv[1]);
+	IPCUnixSocket ipc(ipcfd);
+
+	if (ipc.connect()) {
+		cerr << "Failed to connect to IPC" << endl;
+		return EXIT_FAILURE;
+	}
+
+	bool run = true;
+	while (run) {
+		int ret = 0;
+		IPCUnixSocket::Payload payload, response;
+
+		ret = ipc.recv(&payload, 100);
+		if (ret < 0) {
+			if (ret == -ETIMEDOUT)
+				continue;
+			return ret;
+		}
+		switch (payload.priv) {
+		case CMD_CLOSE:
+			run = false;
+			break;
+		case CMD_REVERESE: {
+			std::string str(payload.data.begin(), payload.data.end());
+			std::reverse(str.begin(), str.end());
+			response.data = std::vector<uint8_t>(str.begin(), str.end());
+			ret = ipc.send(response);
+			if (ret < 0)
+				return ret;
+			break;
+		}
+		case CMD_LEN_CALC: {
+			int size = 0;
+			for (int fd : payload.fds)
+				size += calcLength(fd);
+
+			response.data.resize(sizeof(size));
+			memcpy(response.data.data(), &size, sizeof(size));
+			ret = ipc.send(response);
+			if (ret < 0)
+				return ret;
+			break;
+		}
+		case CMD_LEN_CMP: {
+			int size = 0;
+			for (int fd : payload.fds)
+				size += calcLength(fd);
+
+			int cmp;
+			memcpy(&cmp, payload.data.data(), sizeof(cmp));
+
+			if (cmp != size)
+				return -ERANGE;
+			break;
+		}
+		default:
+			cerr << "Unkown command " << payload.priv << endl;
+			return -EINVAL;
+		}
+	}
+
+	ipc.close();
+
+	return 0;
+}
diff --git a/test/ipc/unixsocket.cpp b/test/ipc/unixsocket.cpp
new file mode 100644
index 0000000000000000..ad2609764166a852
--- /dev/null
+++ b/test/ipc/unixsocket.cpp
@@ -0,0 +1,200 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * unixsocket.cpp - Unix socket IPC test
+ */
+
+#include "unixsocket.h"
+
+#include <fcntl.h>
+#include <iostream>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "ipc_unixsocket.h"
+#include "test.h"
+
+#define MASTER_BIN IPC_TEST_DIR "/unixsocket"
+#define SLAVE_BIN IPC_TEST_DIR "/unixsocket-slave"
+
+using namespace std;
+using namespace libcamera;
+
+class UnixSocketTest : public Test
+{
+protected:
+	int slaveStart(int fd)
+	{
+		pid_ = fork();
+
+		if (pid_ == -1)
+			return TestFail;
+
+		if (!pid_) {
+			std::string arg = std::to_string(fd);
+			execl(SLAVE_BIN, SLAVE_BIN, arg.c_str());
+
+			/* Only get here if exec fails. */
+			exit(TestFail);
+		}
+
+		return TestPass;
+	}
+
+	int slaveStop()
+	{
+		int status;
+
+		if (pid_ < 0)
+			return TestFail;
+
+		if (waitpid(pid_, &status, 0) < 0)
+			return TestFail;
+
+		if (!WIFEXITED(status) || WEXITSTATUS(status))
+			return TestFail;
+
+		return TestPass;
+	}
+
+	int testReverse()
+	{
+		std::string input = "FooBar";
+		std::string match = "raBooF";
+
+		IPCUnixSocket::Payload payload, response;
+
+		payload.priv = CMD_REVERESE;
+		payload.data = std::vector<uint8_t>(input.begin(), input.end());
+
+		if (ipc_.call(payload, &response, 100))
+			return TestFail;
+
+		std::string output(response.data.begin(), response.data.end());
+
+		if (output != match)
+			return TestFail;
+
+		return 0;
+	}
+
+	int testCalc()
+	{
+		int fdM = open(MASTER_BIN, O_RDONLY);
+		int fdS = open(SLAVE_BIN, O_RDONLY);
+
+		if (fdM < 0 || fdS < 0)
+			return TestFail;
+
+		int size = 0;
+		size += calcLength(fdM);
+		size += calcLength(fdS);
+
+		IPCUnixSocket::Payload payload, response;
+
+		payload.priv = CMD_LEN_CALC;
+		payload.fds.push_back(fdM);
+		payload.fds.push_back(fdS);
+
+		if (ipc_.call(payload, &response, 100))
+			return TestFail;
+
+		int output;
+		memcpy(&output, response.data.data(), sizeof(output));
+
+		if (output != size)
+			return TestFail;
+
+		return 0;
+	}
+
+	int testCmp()
+	{
+		int fdM = open(MASTER_BIN, O_RDONLY);
+		int fdS = open(SLAVE_BIN, O_RDONLY);
+
+		if (fdM < 0 || fdS < 0)
+			return TestFail;
+
+		int size = 0;
+		size += calcLength(fdM);
+		size += calcLength(fdS);
+
+		IPCUnixSocket::Payload payload, response;
+
+		payload.priv = CMD_LEN_CMP;
+		payload.data.resize(sizeof(size));
+		memcpy(payload.data.data(), &size, sizeof(size));
+		payload.fds.push_back(fdM);
+		payload.fds.push_back(fdS);
+
+		if (ipc_.send(payload))
+			return TestFail;
+
+		return 0;
+	}
+
+	int testClose()
+	{
+		IPCUnixSocket::Payload payload;
+
+		payload.priv = CMD_CLOSE;
+
+		if (ipc_.send(payload))
+			return TestFail;
+
+		return 0;
+	}
+
+	int run()
+	{
+		int slavefd;
+
+		slavefd = ipc_.create();
+		if (slavefd < 0)
+			return TestFail;
+
+		if (slaveStart(slavefd))
+			return TestFail;
+
+		if (ipc_.connect()) {
+			cerr << "Failed to connect to IPC" << endl;
+			return TestFail;
+		}
+		if (testReverse()) {
+			cerr << "String reverse fail" << endl;
+			return TestFail;
+		}
+
+		if (testCalc()) {
+			cerr << "Size calc fail" << endl;
+			return TestFail;
+		}
+
+		if (testCmp()) {
+			cerr << "Compare fail" << endl;
+			return TestFail;
+		}
+
+		if (testClose())
+			return TestFail;
+
+		printf("Master OK!\n");
+
+		ipc_.close();
+
+		if (slaveStop())
+			return TestFail;
+
+		return TestPass;
+	}
+
+private:
+	pid_t pid_;
+	IPCUnixSocket ipc_;
+};
+
+TEST_REGISTER(UnixSocketTest)
diff --git a/test/ipc/unixsocket.h b/test/ipc/unixsocket.h
new file mode 100644
index 0000000000000000..5ae223c76108a4f6
--- /dev/null
+++ b/test/ipc/unixsocket.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * unixsocket.h - Unix socket IPC test
+ *
+ */
+#ifndef __LIBCAMERA_IPCUNIXSOCKET_TEST_H__
+#define __LIBCAMERA_IPCUNIXSOCKET_TEST_H__
+
+#include <unistd.h>
+
+#define CMD_CLOSE 0
+#define CMD_REVERESE 1
+#define CMD_LEN_CALC 2
+#define CMD_LEN_CMP 3
+
+int calcLength(int fd)
+{
+	lseek(fd, 0, 0);
+	int size = lseek(fd, 0, SEEK_END);
+	lseek(fd, 0, 0);
+
+	return size;
+}
+
+#endif /* __LIBCAMERA_IPCUNIXSOCKET_TEST_H__ */
diff --git a/test/meson.build b/test/meson.build
index c36ac24796367501..3666f6b2385bd4ca 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -2,6 +2,7 @@ subdir('libtest')
 
 subdir('camera')
 subdir('ipa')
+subdir('ipc')
 subdir('media_device')
 subdir('pipeline')
 subdir('stream')
