diff --git a/repos/base/run/migrate.run b/repos/base/run/migrate.run
new file mode 100644
index 0000000000..cc3cd573d3
--- /dev/null
+++ b/repos/base/run/migrate.run
@@ -0,0 +1,62 @@
+build "core init test/migrate timer"
+
+if {![have_include "power_on/qemu"]} {
+ puts "Run script is not supported on this platform"
+ exit 0
+}
+if {[have_spec foc] && ([have_spec pbxa9] || [have_spec rpi3])} {
+ # foc kernel does detect solely 1 CPU */
+ puts "Run script is not supported on this platform"
+ exit 0
+}
+if {![have_spec nova] && ![have_spec foc] && ![have_spec sel4]} {
+ puts "Run script is not supported on this platform"
+ exit 0
+}
+
+create_boot_directory
+
+append config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+install_config $config
+
+build_boot_image "core ld.lib.so init test-migrate timer"
+
+append qemu_args "-nographic "
+append qemu_args "-smp 4,cores=4,threads=1"
+
+run_genode_until {.*test completed successfully.*\n} 70
+
+grep_output {^\[init -> test-migrate\] \[ep\] thread 'migrate' migrated, .*}
+
+compare_output_to {
+[init -> test-migrate] [ep] thread 'migrate' migrated, location=1x0
+[init -> test-migrate] [ep] thread 'migrate' migrated, location=2x0
+[init -> test-migrate] [ep] thread 'migrate' migrated, location=3x0
+[init -> test-migrate] [ep] thread 'migrate' migrated, location=0x0
+}
diff --git a/repos/base/src/test/migrate/main.cc b/repos/base/src/test/migrate/main.cc
new file mode 100644
index 0000000000..79d4dbac74
--- /dev/null
+++ b/repos/base/src/test/migrate/main.cc
@@ -0,0 +1,173 @@
+/*
+ * \brief Testing thread migration
+ * \author Alexander Boettcher
+ * \date 2020-08-13
+ */
+
+/*
+ * Copyright (C) 2020 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+/* Genode includes */
+#include
+#include
+#include
+#include
+#include
+
+using namespace Genode;
+
+
+enum { STACK_SIZE = 0x3000 };
+
+/************************************************
+ ** Migrate thread over all available CPU test **
+ ************************************************/
+
+struct Migrate_thread : Thread
+{
+ Blockade blockade { };
+ Env &env;
+
+ Migrate_thread(Env &env)
+ : Thread(env, "migrate", STACK_SIZE), env(env)
+ { }
+
+ void entry() override
+ {
+ Cpu_thread_client thread_client(cap());
+
+ /* we are ready */
+ blockade.wakeup();
+
+ while (true) {
+ log("[migrate] going to sleep");
+
+ blockade.block();
+
+ log("[migrate] woke up - got migrated ?");
+ }
+ }
+};
+
+struct Migrate
+{
+ Env &env;
+ Timer::Connection timer { env };
+ Migrate_thread thread { env };
+ Trace::Connection trace { env, 15 * 4096 /* RAM quota */,
+ 11 * 4096 /* ARG_BUFFER RAM quota */,
+ 0 /* parent levels */ };
+
+ Signal_handler timer_handler { env.ep(), *this,
+ &Migrate::check_traces };
+
+ Trace::Subject_id trace_id { };
+ Affinity::Location location { };
+ unsigned loc_same { 0 };
+ unsigned loc_pos { 0 };
+
+ unsigned test_rounds { 0 };
+
+ enum State {
+ LOOKUP_TRACE_ID, CHECK_AFFINITY, MIGRATE
+ } state { LOOKUP_TRACE_ID };
+
+ Migrate(Env &env) : env(env)
+ {
+ Affinity::Space cpus = env.cpu().affinity_space();
+ log("Detected ", cpus.width(), "x", cpus.height(), " CPU",
+ cpus.total() > 1 ? "s." : ".");
+
+ timer.sigh(timer_handler);
+
+ thread.start();
+ thread.blockade.block();
+
+ timer.trigger_periodic( 500 * 1000 /* us */);
+ }
+
+ void check_traces()
+ {
+ switch (state) {
+ case LOOKUP_TRACE_ID:
+ {
+ auto count = trace.for_each_subject_info([&](Trace::Subject_id const &id,
+ Trace::Subject_info const &info)
+ {
+ if (info.thread_name() != "migrate")
+ return;
+
+ trace_id = id;
+ location = info.affinity();
+ state = CHECK_AFFINITY;
+
+ log("[ep] thread '", info.thread_name(), "' started,",
+ " location=", location.xpos(), "x", location.ypos());
+ });
+
+ if (count.count == count.limit && state == LOOKUP_TRACE_ID) {
+ error("trace argument buffer too small for the test");
+ }
+ break;
+ }
+ case CHECK_AFFINITY:
+ {
+ Trace::Subject_info const info = trace.subject_info(trace_id);
+
+ loc_same ++;
+
+ if ((location.xpos() == info.affinity().xpos()) &&
+ (location.ypos() == info.affinity().ypos()) &&
+ (location.width() == info.affinity().width()) &&
+ (location.height() == info.affinity().height()))
+ {
+ if (loc_same >= 1) {
+ loc_same = 0;
+ state = MIGRATE;
+ }
+ log ("[ep] .");
+ break;
+ }
+
+ loc_same = 0;
+ location = info.affinity();
+
+ log("[ep] thread '", info.thread_name(), "' migrated,",
+ " location=", location.xpos(), "x", location.ypos());
+
+ test_rounds ++;
+ if (test_rounds == 4)
+ log("--- test completed successfully ---");
+ break;
+ }
+ case MIGRATE:
+ state = CHECK_AFFINITY;
+
+ loc_pos ++;
+ Affinity::Location const loc = env.cpu().affinity_space().location_of_index(loc_pos);
+
+ /* trigger migration */
+ Cpu_thread_client client(thread.cap());
+ client.affinity(loc);
+
+ log("[ep] thread 'migrate' scheduled to migrate to location=",
+ loc.xpos(), "x", loc.ypos());
+
+ thread.blockade.wakeup();
+
+ break;
+ }
+ }
+};
+
+
+void Component::construct(Env &env)
+{
+ log("--- migrate thread test started ---");
+
+ static Migrate migrate_test(env);
+}
diff --git a/repos/base/src/test/migrate/target.mk b/repos/base/src/test/migrate/target.mk
new file mode 100644
index 0000000000..6ae4d742a4
--- /dev/null
+++ b/repos/base/src/test/migrate/target.mk
@@ -0,0 +1,3 @@
+TARGET = test-migrate
+SRC_CC = main.cc
+LIBS = base
diff --git a/tool/autopilot.list b/tool/autopilot.list
index 83d782dc20..3d5ff01d67 100644
--- a/tool/autopilot.list
+++ b/tool/autopilot.list
@@ -22,6 +22,7 @@ lx_hybrid_ctors
lx_hybrid_exception
lx_hybrid_pthread_ipc
microcode
+migrate
moon
netperf_lwip
netperf_lwip_bridge