diff --git a/configure.ac b/configure.ac
index 78ff245..c03778f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -138,6 +138,20 @@ AC_ARG_ENABLE([test-perf],
 AM_CONDITIONAL([test_perf], [test x$test_perf = xyes ])
 
 ##########################################################################
+# Enable/disable test-helper
+##########################################################################
+test_helper=no
+AC_ARG_ENABLE([test-helper],
+    [  --enable-test-helper      run test in test/helper],
+    [if test "x$enableval" = "xyes"; then
+        test_helper=yes
+    fi])
+
+AM_CONDITIONAL([test_helper], [test x$test_helper = xyes ])
+
+##########################################################################
+
+##########################################################################
 # Enable/disable test-cpp
 ##########################################################################
 test_cpp=no
@@ -284,6 +298,7 @@ AC_CONFIG_FILES([Makefile
 		 test/performance/Makefile
 		 test/validation/Makefile
 		 test/miscellaneous/Makefile
+		 test/helper/Makefile
 		 ])
 
 AC_SEARCH_LIBS([timer_create],[rt posix4])
diff --git a/test/Makefile.am b/test/Makefile.am
index 2ba8008..31872b0 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -1,4 +1,7 @@
-SUBDIRS = api_test performance miscellaneous
+SUBDIRS = api_test \
+          performance \
+          miscellaneous \
+          helper
 
 if cunit_support
     SUBDIRS += validation
diff --git a/test/helper/.gitignore b/test/helper/.gitignore
new file mode 100644
index 0000000..fe65f30
--- /dev/null
+++ b/test/helper/.gitignore
@@ -0,0 +1,4 @@
+*.trs
+*.log
+odp_process
+odp_thread
diff --git a/test/helper/Makefile.am b/test/helper/Makefile.am
new file mode 100644
index 0000000..f330533
--- /dev/null
+++ b/test/helper/Makefile.am
@@ -0,0 +1,25 @@
+include $(top_srcdir)/test/Makefile.inc
+
+AM_CFLAGS += -I$(srcdir)/common
+AM_LDFLAGS += -static
+
+TESTS_ENVIRONMENT = ODP_PLATFORM=${with_platform} TEST_DIR=${builddir}
+
+EXECUTABLES = odp_thread \
+              odp_process
+
+COMPILE_ONLY =
+
+TESTSCRIPTS =
+
+if test_helper
+TESTS = $(EXECUTABLES) $(TESTSCRIPTS)
+endif
+
+dist_bin_SCRIPTS =
+
+bin_PROGRAMS = $(EXECUTABLES) $(COMPILE_ONLY)
+
+
+dist_odp_thread_SOURCES = odp_thread.c
+dist_odp_process_SOURCES = odp_process.c
diff --git a/test/helper/odp_process.c b/test/helper/odp_process.c
new file mode 100644
index 0000000..495cb00
--- /dev/null
+++ b/test/helper/odp_process.c
@@ -0,0 +1,85 @@
+/* Copyright (c) 2015, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+#include <test_debug.h>
+#include <odp.h>
+#include <odp/helper/linux.h>
+
+#define NUMBER_WORKERS 16 /* 0 = max */
+
+static void *worker_fn(void *arg TEST_UNUSED)
+{
+	/* depend on the odp helper to call odp_init_local */
+	printf("Worker thread on CPU %d\n", odp_cpu_id());
+
+	return 0;
+}
+
+/* Create additional dataplane processes */
+int main(int argc TEST_UNUSED, char *argv[] TEST_UNUSED)
+{
+	odp_cpumask_t cpu_mask;
+	int num_workers;
+	int cpu;
+	char cpumaskstr[ODP_CPUMASK_STR_SIZE];
+	int ret;
+	odph_linux_process_t proc[NUMBER_WORKERS];
+
+	if (odp_init_global(NULL, NULL)) {
+		LOG_ERR("Error: ODP global init failed.\n");
+		exit(EXIT_FAILURE);
+	}
+
+	if (odp_init_local()) {
+		LOG_ERR("Error: ODP local init failed.\n");
+		exit(EXIT_FAILURE);
+	}
+
+	/* discover how many threads this system can support */
+	num_workers = odph_linux_cpumask_default(&cpu_mask, NUMBER_WORKERS);
+	if (num_workers < NUMBER_WORKERS) {
+		printf("System can only support %d threads and not the %d requested\n",
+		       num_workers, NUMBER_WORKERS);
+	}
+
+	/* generate a summary for the user */
+	(void)odp_cpumask_to_str(&cpu_mask, cpumaskstr, sizeof(cpumaskstr));
+	printf("default cpu mask:           %s\n", cpumaskstr);
+	printf("default num worker threads: %i\n", num_workers);
+
+	cpu = odp_cpumask_first(&cpu_mask);
+	printf("the first CPU:              %i\n", cpu);
+
+	/* reserve cpu 0 for the control plane so remove it from the default mask */
+	odp_cpumask_clr(&cpu_mask, 0);
+	num_workers = odp_cpumask_count(&cpu_mask);
+	(void)odp_cpumask_to_str(&cpu_mask, cpumaskstr, sizeof(cpumaskstr));
+	printf("new cpu mask:               %s\n", cpumaskstr);
+	printf("new num worker threads:     %i\n\n", num_workers);
+
+	/* Fork worker processes */
+	ret = odph_linux_process_fork_n(proc, &cpu_mask);
+
+	if (ret < 0) {
+		LOG_ERR("Fork workers failed %i\n", ret);
+		return -1;
+	}
+
+	if (ret == 0) {
+		/* Child process */
+		worker_fn(NULL);
+	} else {
+		/* Parent process */
+		odph_linux_process_wait_n(proc, num_workers);
+
+		if (odp_term_global()) {
+			LOG_ERR("Error: ODP global term failed.\n");
+			exit(EXIT_FAILURE);
+		}
+	}
+
+	return 0;
+}
diff --git a/test/helper/odp_thread.c b/test/helper/odp_thread.c
new file mode 100644
index 0000000..04c6b1e
--- /dev/null
+++ b/test/helper/odp_thread.c
@@ -0,0 +1,79 @@
+/* Copyright (c) 2015, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+#include <test_debug.h>
+#include <odp.h>
+#include <odp/helper/linux.h>
+
+#define NUMBER_WORKERS 16
+static void *worker_fn(void *arg TEST_UNUSED)
+{
+	/* depend on the odp helper to call odp_init_local */
+
+	printf("Worker thread on CPU %d\n", odp_cpu_id());
+
+	/* depend on the odp helper to call odp_term_local */
+
+	return 0;
+}
+
+/* Create additional dataplane threads */
+int main(int argc TEST_UNUSED, char *argv[] TEST_UNUSED)
+{
+	odph_linux_pthread_t thread_tbl[NUMBER_WORKERS];
+	odp_cpumask_t cpu_mask;
+	int num_workers;
+	int cpu;
+	char cpumaskstr[ODP_CPUMASK_STR_SIZE];
+
+	if (odp_init_global(NULL, NULL)) {
+		LOG_ERR("Error: ODP global init failed.\n");
+		exit(EXIT_FAILURE);
+	}
+
+	if (odp_init_local()) {
+		LOG_ERR("Error: ODP local init failed.\n");
+		exit(EXIT_FAILURE);
+	}
+
+	/* discover how many threads this system can support */
+	num_workers = odph_linux_cpumask_default(&cpu_mask, NUMBER_WORKERS);
+	if (num_workers < NUMBER_WORKERS) {
+		printf("System can only support %d threads and not the %d requested\n",
+		       num_workers, NUMBER_WORKERS);
+	}
+
+	/* generate a summary for the user */
+	(void)odp_cpumask_to_str(&cpu_mask, cpumaskstr, sizeof(cpumaskstr));
+	printf("default cpu mask:           %s\n", cpumaskstr);
+	printf("default num worker threads: %i\n", num_workers);
+
+	cpu = odp_cpumask_first(&cpu_mask);
+	printf("the first CPU:              %i\n", cpu);
+
+	/* reserve cpu 0 for the control plane so remove it from the default mask */
+	odp_cpumask_clr(&cpu_mask, 0);
+	num_workers = odp_cpumask_count(&cpu_mask);
+	(void)odp_cpumask_to_str(&cpu_mask, cpumaskstr, sizeof(cpumaskstr));
+	printf("new cpu mask:               %s\n", cpumaskstr);
+	printf("new num worker threads:     %i\n\n", num_workers);
+
+	odph_linux_pthread_create(&thread_tbl[0], &cpu_mask, worker_fn, NULL);
+
+	odph_linux_pthread_join(thread_tbl, num_workers);
+
+	if (odp_term_local()) {
+		LOG_ERR("Error: ODP local term failed.\n");
+		exit(EXIT_FAILURE);
+	}
+
+	if (odp_term_global()) {
+		LOG_ERR("Error: ODP global term failed.\n");
+		exit(EXIT_FAILURE);
+	}
+
+	return 0;
+}
