diff mbox series

[v2,5/5] tests/functional: Convert the migration avocado test

Message ID 20241218131439.255841-6-thuth@redhat.com (mailing list archive)
State New
Headers show
Series tests/functional: Convert tests with find_free_ports() | expand

Commit Message

Thomas Huth Dec. 18, 2024, 1:14 p.m. UTC
Now that we've got a find_free_port() function in the functional
test framework, we can convert the migration test, too.
While the original avocado test was only meant to run on aarch64,
ppc64 and x86, we can turn this into a more generic test by now
and run it on all architectures that have a default machine that
ships with a working firmware.

Signed-off-by: Thomas Huth <thuth@redhat.com>
---
 MAINTAINERS                                   |   1 +
 tests/functional/meson.build                  |  19 +++
 .../test_migration.py}                        | 121 +++++-------------
 3 files changed, 54 insertions(+), 87 deletions(-)
 rename tests/{avocado/migration.py => functional/test_migration.py} (41%)
 mode change 100644 => 100755

Comments

Fabiano Rosas Dec. 18, 2024, 1:51 p.m. UTC | #1
Thomas Huth <thuth@redhat.com> writes:

> Now that we've got a find_free_port() function in the functional
> test framework, we can convert the migration test, too.
> While the original avocado test was only meant to run on aarch64,
> ppc64 and x86, we can turn this into a more generic test by now
> and run it on all architectures that have a default machine that
> ships with a working firmware.

I'd rather drop this test. I haven't looked at it in ages and it has
never been useful. I haven't been following the development of the
functional suite so this might not apply this time (fingers crossed),
but Python tests have always been a pain to work with.

About adding more architectures to the set, this is not simply enabling
more testing, it is also adding workload to maintain these other arches
that were never tested with migration. Is that something we want?

Also note that what is actually prone to break is compatibility between
versions, which is not covered by this test.

>
> Signed-off-by: Thomas Huth <thuth@redhat.com>
> ---
>  MAINTAINERS                                   |   1 +
>  tests/functional/meson.build                  |  19 +++
>  .../test_migration.py}                        | 121 +++++-------------
>  3 files changed, 54 insertions(+), 87 deletions(-)
>  rename tests/{avocado/migration.py => functional/test_migration.py} (41%)
>  mode change 100644 => 100755
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 389b390de1..704648d57a 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -3429,6 +3429,7 @@ F: include/migration/
>  F: include/qemu/userfaultfd.h
>  F: migration/
>  F: scripts/vmstate-static-checker.py
> +F: tests/functional/test_migration.py
>  F: tests/vmstate-static-checker-data/
>  F: tests/qtest/migration/
>  F: tests/qtest/migration-*
> diff --git a/tests/functional/meson.build b/tests/functional/meson.build
> index 781bd7eae6..8c3d1c26da 100644
> --- a/tests/functional/meson.build
> +++ b/tests/functional/meson.build
> @@ -70,6 +70,10 @@ tests_aarch64_system_thorough = [
>    'multiprocess',
>  ]
>  
> +tests_alpha_system_quick = [
> +  'migration',
> +]
> +
>  tests_alpha_system_thorough = [
>    'alpha_clipper',
>  ]
> @@ -167,6 +171,10 @@ tests_ppc_system_thorough = [
>    'ppc_virtex_ml507',
>  ]
>  
> +tests_ppc64_system_quick = [
> +  'migration',
> +]
> +
>  tests_ppc64_system_thorough = [
>    'ppc64_e500',
>    'ppc64_hv',
> @@ -176,6 +184,7 @@ tests_ppc64_system_thorough = [
>  ]
>  
>  tests_riscv32_system_quick = [
> +  'migration',
>    'riscv_opensbi',
>  ]
>  
> @@ -184,6 +193,7 @@ tests_riscv32_system_thorough = [
>  ]
>  
>  tests_riscv64_system_quick = [
> +  'migration',
>    'riscv_opensbi',
>  ]
>  
> @@ -210,10 +220,18 @@ tests_sh4eb_system_thorough = [
>    'sh4eb_r2d',
>  ]
>  
> +tests_sparc_system_quick = [
> +  'migration',
> +]
> +
>  tests_sparc_system_thorough = [
>    'sparc_sun4m',
>  ]
>  
> +tests_sparc64_system_quick = [
> +  'migration',
> +]
> +
>  tests_sparc64_system_thorough = [
>    'sparc64_sun4u',
>    'sparc64_tuxrun',
> @@ -222,6 +240,7 @@ tests_sparc64_system_thorough = [
>  tests_x86_64_system_quick = [
>    'cpu_queries',
>    'mem_addr_space',
> +  'migration',
>    'pc_cpu_hotplug_props',
>    'virtio_version',
>    'x86_cpu_model_versions',
> diff --git a/tests/avocado/migration.py b/tests/functional/test_migration.py
> old mode 100644
> new mode 100755
> similarity index 41%
> rename from tests/avocado/migration.py
> rename to tests/functional/test_migration.py
> index be6234b3c2..5607d601eb
> --- a/tests/avocado/migration.py
> +++ b/tests/functional/test_migration.py
> @@ -1,3 +1,5 @@
> +#!/usr/bin/env python3
> +#
>  # Migration test
>  #
>  # Copyright (c) 2019 Red Hat, Inc.
> @@ -9,22 +11,14 @@
>  # This work is licensed under the terms of the GNU GPL, version 2 or
>  # later.  See the COPYING file in the top-level directory.
>  
> -
> -import tempfile
>  import os
> +import tempfile
> +import time
>  
> -from avocado_qemu import QemuSystemTest
> -from avocado import skipUnless
> -
> -from avocado.utils.network import ports
> -from avocado.utils import wait
> -from avocado.utils.path import find_command
> -
> +from qemu_test import QemuSystemTest, skipIfMissingCommands
> +from qemu_test.ports import Ports
>  
>  class MigrationTest(QemuSystemTest):
> -    """
> -    :avocado: tags=migration
> -    """
>  
>      timeout = 10
>  
> @@ -33,103 +27,56 @@ def migration_finished(vm):
>          return vm.cmd('query-migrate')['status'] in ('completed', 'failed')
>  
>      def assert_migration(self, src_vm, dst_vm):
> -        wait.wait_for(self.migration_finished,
> -                      timeout=self.timeout,
> -                      step=0.1,
> -                      args=(src_vm,))
> -        wait.wait_for(self.migration_finished,
> -                      timeout=self.timeout,
> -                      step=0.1,
> -                      args=(dst_vm,))
> +
> +        end = time.monotonic() + self.timeout
> +        while time.monotonic() < end and not self.migration_finished(src_vm):
> +           time.sleep(0.1)
> +
> +        end = time.monotonic() + self.timeout
> +        while time.monotonic() < end and not self.migration_finished(dst_vm):
> +           time.sleep(0.1)
> +
>          self.assertEqual(src_vm.cmd('query-migrate')['status'], 'completed')
>          self.assertEqual(dst_vm.cmd('query-migrate')['status'], 'completed')
>          self.assertEqual(dst_vm.cmd('query-status')['status'], 'running')
>          self.assertEqual(src_vm.cmd('query-status')['status'],'postmigrate')
>  
>      def do_migrate(self, dest_uri, src_uri=None):
> -        dest_vm = self.get_vm('-incoming', dest_uri)
> +        dest_vm = self.get_vm('-incoming', dest_uri, name="dest-qemu")
>          dest_vm.add_args('-nodefaults')
>          dest_vm.launch()
>          if src_uri is None:
>              src_uri = dest_uri
> -        source_vm = self.get_vm()
> +        source_vm = self.get_vm(name="source-qemu")
>          source_vm.add_args('-nodefaults')
>          source_vm.launch()
>          source_vm.qmp('migrate', uri=src_uri)
>          self.assert_migration(source_vm, dest_vm)
>  
> -    def _get_free_port(self):
> +    def _get_free_port(self, ports):
>          port = ports.find_free_port()
>          if port is None:
> -            self.cancel('Failed to find a free port')
> +            self.skipTest('Failed to find a free port')
>          return port
>  
> -    def migration_with_tcp_localhost(self):
> -        dest_uri = 'tcp:localhost:%u' % self._get_free_port()
> -        self.do_migrate(dest_uri)
> +    def test_migration_with_tcp_localhost(self):
> +        with Ports() as ports:
> +            dest_uri = 'tcp:localhost:%u' % self._get_free_port(ports)
> +            self.do_migrate(dest_uri)
>  
> -    def migration_with_unix(self):
> +    def test_migration_with_unix(self):
>          with tempfile.TemporaryDirectory(prefix='socket_') as socket_path:
>              dest_uri = 'unix:%s/qemu-test.sock' % socket_path
>              self.do_migrate(dest_uri)
>  
> -    @skipUnless(find_command('nc', default=False), "'nc' command not found")
> -    def migration_with_exec(self):
> -        """The test works for both netcat-traditional and netcat-openbsd packages."""
> -        free_port = self._get_free_port()
> -        dest_uri = 'exec:nc -l localhost %u' % free_port
> -        src_uri = 'exec:nc localhost %u' % free_port
> -        self.do_migrate(dest_uri, src_uri)
> -
> -
> -@skipUnless('aarch64' in os.uname()[4], "host != target")
> -class Aarch64(MigrationTest):
> -    """
> -    :avocado: tags=arch:aarch64
> -    :avocado: tags=machine:virt
> -    :avocado: tags=cpu:max
> -    """
> -
> -    def test_migration_with_tcp_localhost(self):
> -        self.migration_with_tcp_localhost()
> -
> -    def test_migration_with_unix(self):
> -        self.migration_with_unix()
> -
> -    def test_migration_with_exec(self):
> -        self.migration_with_exec()
> -
> -
> -@skipUnless('x86_64' in os.uname()[4], "host != target")
> -class X86_64(MigrationTest):
> -    """
> -    :avocado: tags=arch:x86_64
> -    :avocado: tags=machine:pc
> -    :avocado: tags=cpu:qemu64
> -    """
> -
> -    def test_migration_with_tcp_localhost(self):
> -        self.migration_with_tcp_localhost()
> -
> -    def test_migration_with_unix(self):
> -        self.migration_with_unix()
> -
> +    @skipIfMissingCommands('nc')
>      def test_migration_with_exec(self):
> -        self.migration_with_exec()
> -
> -
> -@skipUnless('ppc64le' in os.uname()[4], "host != target")
> -class PPC64(MigrationTest):
> -    """
> -    :avocado: tags=arch:ppc64
> -    :avocado: tags=machine:pseries
> -    """
> -
> -    def test_migration_with_tcp_localhost(self):
> -        self.migration_with_tcp_localhost()
> -
> -    def test_migration_with_unix(self):
> -        self.migration_with_unix()
> -
> -    def test_migration_with_exec(self):
> -        self.migration_with_exec()
> +        """The test works for both netcat-traditional and netcat-openbsd packages."""
> +        with Ports() as ports:
> +            free_port = self._get_free_port(ports)
> +            dest_uri = 'exec:nc -l localhost %u' % free_port
> +            src_uri = 'exec:nc localhost %u' % free_port
> +            self.do_migrate(dest_uri, src_uri)
> +
> +if __name__ == '__main__':
> +    QemuSystemTest.main()
Thomas Huth Dec. 18, 2024, 3:51 p.m. UTC | #2
On 18/12/2024 14.51, Fabiano Rosas wrote:
> Thomas Huth <thuth@redhat.com> writes:
> 
>> Now that we've got a find_free_port() function in the functional
>> test framework, we can convert the migration test, too.
>> While the original avocado test was only meant to run on aarch64,
>> ppc64 and x86, we can turn this into a more generic test by now
>> and run it on all architectures that have a default machine that
>> ships with a working firmware.
> 
> I'd rather drop this test. I haven't looked at it in ages and it has
> never been useful.

I think I agree for the scope of the old avocado test - x86, ppc64 and 
aarch64 certainly have better test coverage by the qtest already... but we 
don't have any test coverage for other architectures at all yet, which is 
bad (see below).

So if you like I can change the patch so that the test is not run on x86, 
ppc64 and aarch64 anymore, just on the other architectures that do not have 
test coverage by the qtest yet?

> I haven't been following the development of the
> functional suite so this might not apply this time (fingers crossed),
> but Python tests have always been a pain to work with.

Well, one of the motivations with the functional test framework was to 
simplify things. You can now run the individual tests without any test 
runner at all, what makes debugging way easier (see 
docs/devel/testing/functional.rst for details)!

> About adding more architectures to the set, this is not simply enabling
> more testing, it is also adding workload to maintain these other arches
> that were never tested with migration. Is that something we want?

I think yes. Otherwise the bugs are just dormant until someone hits the 
issue, making bisection way more complicated later.
Remember this one for example:

  https://mail.gnu.org/archive/html/qemu-commits/2023-02/msg00030.html

?

It would have been good to have a migration test for alpha in the CI, then 
we could have prevented that bug from being merged.

> Also note that what is actually prone to break is compatibility between
> versions, which is not covered by this test.

I think it should be possible to add such a check later.

  Thomas
Daniel P. Berrangé Dec. 18, 2024, 4 p.m. UTC | #3
On Wed, Dec 18, 2024 at 04:51:24PM +0100, Thomas Huth wrote:
> On 18/12/2024 14.51, Fabiano Rosas wrote:
> > Thomas Huth <thuth@redhat.com> writes:
> > 
> > > Now that we've got a find_free_port() function in the functional
> > > test framework, we can convert the migration test, too.
> > > While the original avocado test was only meant to run on aarch64,
> > > ppc64 and x86, we can turn this into a more generic test by now
> > > and run it on all architectures that have a default machine that
> > > ships with a working firmware.
> > 
> > I'd rather drop this test. I haven't looked at it in ages and it has
> > never been useful.
> 
> I think I agree for the scope of the old avocado test - x86, ppc64 and
> aarch64 certainly have better test coverage by the qtest already... but we
> don't have any test coverage for other architectures at all yet, which is
> bad (see below).
> 
> So if you like I can change the patch so that the test is not run on x86,
> ppc64 and aarch64 anymore, just on the other architectures that do not have
> test coverage by the qtest yet?
> 
> > I haven't been following the development of the
> > functional suite so this might not apply this time (fingers crossed),
> > but Python tests have always been a pain to work with.
> 
> Well, one of the motivations with the functional test framework was to
> simplify things. You can now run the individual tests without any test
> runner at all, what makes debugging way easier (see
> docs/devel/testing/functional.rst for details)!
> 
> > About adding more architectures to the set, this is not simply enabling
> > more testing, it is also adding workload to maintain these other arches
> > that were never tested with migration. Is that something we want?
> 
> I think yes. Otherwise the bugs are just dormant until someone hits the
> issue, making bisection way more complicated later.
> Remember this one for example:
> 
>  https://mail.gnu.org/archive/html/qemu-commits/2023-02/msg00030.html
> 
> ?
> 
> It would have been good to have a migration test for alpha in the CI, then
> we could have prevented that bug from being merged.

IIUC, we run the migration-test  qtest for *every* softmmu target.

So, assuming you're referring to alpha guest, we were already
exercising it.

The migration qtest as it exists today is pushing the boundaries of
what a qtest is. I'd actually call the migration qtest a functional
test that happens to use the qtest framework for historical reasons.

The only slight thing that makes it not a functional test is that it
is using a specialized guest, which is just the custom boot sector
that dirties ram.

A true migration functional test is conceptually interesting, as we
have had bugs in the past which only hit when using real guest OS
in certain ways. The trouble is that worst bugs have been pretty
niche, such that its unlikely we would have pre-empatively have
a test combination that would have hit them.


Anyway, I think a true functional test for migration is relevant
to keep, as long as we make it clearly different from the qtest.
A simple smoke test using a real Linux guest is different enough
from our hand crafted boot sector that I think it is valuable
coverage. Even better if we make the functional test add *lots*
of different devices.

With regards,
Daniel
Thomas Huth Dec. 18, 2024, 4:11 p.m. UTC | #4
On 18/12/2024 17.00, Daniel P. Berrangé wrote:
> On Wed, Dec 18, 2024 at 04:51:24PM +0100, Thomas Huth wrote:
>> On 18/12/2024 14.51, Fabiano Rosas wrote:
>>> Thomas Huth <thuth@redhat.com> writes:
>>>
>>>> Now that we've got a find_free_port() function in the functional
>>>> test framework, we can convert the migration test, too.
>>>> While the original avocado test was only meant to run on aarch64,
>>>> ppc64 and x86, we can turn this into a more generic test by now
>>>> and run it on all architectures that have a default machine that
>>>> ships with a working firmware.
>>>
>>> I'd rather drop this test. I haven't looked at it in ages and it has
>>> never been useful.
>>
>> I think I agree for the scope of the old avocado test - x86, ppc64 and
>> aarch64 certainly have better test coverage by the qtest already... but we
>> don't have any test coverage for other architectures at all yet, which is
>> bad (see below).
>>
>> So if you like I can change the patch so that the test is not run on x86,
>> ppc64 and aarch64 anymore, just on the other architectures that do not have
>> test coverage by the qtest yet?
>>
>>> I haven't been following the development of the
>>> functional suite so this might not apply this time (fingers crossed),
>>> but Python tests have always been a pain to work with.
>>
>> Well, one of the motivations with the functional test framework was to
>> simplify things. You can now run the individual tests without any test
>> runner at all, what makes debugging way easier (see
>> docs/devel/testing/functional.rst for details)!
>>
>>> About adding more architectures to the set, this is not simply enabling
>>> more testing, it is also adding workload to maintain these other arches
>>> that were never tested with migration. Is that something we want?
>>
>> I think yes. Otherwise the bugs are just dormant until someone hits the
>> issue, making bisection way more complicated later.
>> Remember this one for example:
>>
>>   https://mail.gnu.org/archive/html/qemu-commits/2023-02/msg00030.html
>>
>> ?
>>
>> It would have been good to have a migration test for alpha in the CI, then
>> we could have prevented that bug from being merged.
> 
> IIUC, we run the migration-test  qtest for *every* softmmu target.
> 
> So, assuming you're referring to alpha guest, we were already
> exercising it.

Unless I missed something, you got that wrong. Have a look at 
tests/qtest/meson.build: The migration-test is only added for i386/x86_64, 
ppc64, aarch64 and s390x (since you need a special boot / guest code for 
this test).

> Anyway, I think a true functional test for migration is relevant
> to keep, as long as we make it clearly different from the qtest.
> A simple smoke test using a real Linux guest is different enough
> from our hand crafted boot sector that I think it is valuable
> coverage. Even better if we make the functional test add *lots*
> of different devices.

Agreed, that's a good idea. But for a start, I'd first like to convert the 
avocado test so that we finally can clean the tests/avocado folder.

  Thomas
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 389b390de1..704648d57a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3429,6 +3429,7 @@  F: include/migration/
 F: include/qemu/userfaultfd.h
 F: migration/
 F: scripts/vmstate-static-checker.py
+F: tests/functional/test_migration.py
 F: tests/vmstate-static-checker-data/
 F: tests/qtest/migration/
 F: tests/qtest/migration-*
diff --git a/tests/functional/meson.build b/tests/functional/meson.build
index 781bd7eae6..8c3d1c26da 100644
--- a/tests/functional/meson.build
+++ b/tests/functional/meson.build
@@ -70,6 +70,10 @@  tests_aarch64_system_thorough = [
   'multiprocess',
 ]
 
+tests_alpha_system_quick = [
+  'migration',
+]
+
 tests_alpha_system_thorough = [
   'alpha_clipper',
 ]
@@ -167,6 +171,10 @@  tests_ppc_system_thorough = [
   'ppc_virtex_ml507',
 ]
 
+tests_ppc64_system_quick = [
+  'migration',
+]
+
 tests_ppc64_system_thorough = [
   'ppc64_e500',
   'ppc64_hv',
@@ -176,6 +184,7 @@  tests_ppc64_system_thorough = [
 ]
 
 tests_riscv32_system_quick = [
+  'migration',
   'riscv_opensbi',
 ]
 
@@ -184,6 +193,7 @@  tests_riscv32_system_thorough = [
 ]
 
 tests_riscv64_system_quick = [
+  'migration',
   'riscv_opensbi',
 ]
 
@@ -210,10 +220,18 @@  tests_sh4eb_system_thorough = [
   'sh4eb_r2d',
 ]
 
+tests_sparc_system_quick = [
+  'migration',
+]
+
 tests_sparc_system_thorough = [
   'sparc_sun4m',
 ]
 
+tests_sparc64_system_quick = [
+  'migration',
+]
+
 tests_sparc64_system_thorough = [
   'sparc64_sun4u',
   'sparc64_tuxrun',
@@ -222,6 +240,7 @@  tests_sparc64_system_thorough = [
 tests_x86_64_system_quick = [
   'cpu_queries',
   'mem_addr_space',
+  'migration',
   'pc_cpu_hotplug_props',
   'virtio_version',
   'x86_cpu_model_versions',
diff --git a/tests/avocado/migration.py b/tests/functional/test_migration.py
old mode 100644
new mode 100755
similarity index 41%
rename from tests/avocado/migration.py
rename to tests/functional/test_migration.py
index be6234b3c2..5607d601eb
--- a/tests/avocado/migration.py
+++ b/tests/functional/test_migration.py
@@ -1,3 +1,5 @@ 
+#!/usr/bin/env python3
+#
 # Migration test
 #
 # Copyright (c) 2019 Red Hat, Inc.
@@ -9,22 +11,14 @@ 
 # This work is licensed under the terms of the GNU GPL, version 2 or
 # later.  See the COPYING file in the top-level directory.
 
-
-import tempfile
 import os
+import tempfile
+import time
 
-from avocado_qemu import QemuSystemTest
-from avocado import skipUnless
-
-from avocado.utils.network import ports
-from avocado.utils import wait
-from avocado.utils.path import find_command
-
+from qemu_test import QemuSystemTest, skipIfMissingCommands
+from qemu_test.ports import Ports
 
 class MigrationTest(QemuSystemTest):
-    """
-    :avocado: tags=migration
-    """
 
     timeout = 10
 
@@ -33,103 +27,56 @@  def migration_finished(vm):
         return vm.cmd('query-migrate')['status'] in ('completed', 'failed')
 
     def assert_migration(self, src_vm, dst_vm):
-        wait.wait_for(self.migration_finished,
-                      timeout=self.timeout,
-                      step=0.1,
-                      args=(src_vm,))
-        wait.wait_for(self.migration_finished,
-                      timeout=self.timeout,
-                      step=0.1,
-                      args=(dst_vm,))
+
+        end = time.monotonic() + self.timeout
+        while time.monotonic() < end and not self.migration_finished(src_vm):
+           time.sleep(0.1)
+
+        end = time.monotonic() + self.timeout
+        while time.monotonic() < end and not self.migration_finished(dst_vm):
+           time.sleep(0.1)
+
         self.assertEqual(src_vm.cmd('query-migrate')['status'], 'completed')
         self.assertEqual(dst_vm.cmd('query-migrate')['status'], 'completed')
         self.assertEqual(dst_vm.cmd('query-status')['status'], 'running')
         self.assertEqual(src_vm.cmd('query-status')['status'],'postmigrate')
 
     def do_migrate(self, dest_uri, src_uri=None):
-        dest_vm = self.get_vm('-incoming', dest_uri)
+        dest_vm = self.get_vm('-incoming', dest_uri, name="dest-qemu")
         dest_vm.add_args('-nodefaults')
         dest_vm.launch()
         if src_uri is None:
             src_uri = dest_uri
-        source_vm = self.get_vm()
+        source_vm = self.get_vm(name="source-qemu")
         source_vm.add_args('-nodefaults')
         source_vm.launch()
         source_vm.qmp('migrate', uri=src_uri)
         self.assert_migration(source_vm, dest_vm)
 
-    def _get_free_port(self):
+    def _get_free_port(self, ports):
         port = ports.find_free_port()
         if port is None:
-            self.cancel('Failed to find a free port')
+            self.skipTest('Failed to find a free port')
         return port
 
-    def migration_with_tcp_localhost(self):
-        dest_uri = 'tcp:localhost:%u' % self._get_free_port()
-        self.do_migrate(dest_uri)
+    def test_migration_with_tcp_localhost(self):
+        with Ports() as ports:
+            dest_uri = 'tcp:localhost:%u' % self._get_free_port(ports)
+            self.do_migrate(dest_uri)
 
-    def migration_with_unix(self):
+    def test_migration_with_unix(self):
         with tempfile.TemporaryDirectory(prefix='socket_') as socket_path:
             dest_uri = 'unix:%s/qemu-test.sock' % socket_path
             self.do_migrate(dest_uri)
 
-    @skipUnless(find_command('nc', default=False), "'nc' command not found")
-    def migration_with_exec(self):
-        """The test works for both netcat-traditional and netcat-openbsd packages."""
-        free_port = self._get_free_port()
-        dest_uri = 'exec:nc -l localhost %u' % free_port
-        src_uri = 'exec:nc localhost %u' % free_port
-        self.do_migrate(dest_uri, src_uri)
-
-
-@skipUnless('aarch64' in os.uname()[4], "host != target")
-class Aarch64(MigrationTest):
-    """
-    :avocado: tags=arch:aarch64
-    :avocado: tags=machine:virt
-    :avocado: tags=cpu:max
-    """
-
-    def test_migration_with_tcp_localhost(self):
-        self.migration_with_tcp_localhost()
-
-    def test_migration_with_unix(self):
-        self.migration_with_unix()
-
-    def test_migration_with_exec(self):
-        self.migration_with_exec()
-
-
-@skipUnless('x86_64' in os.uname()[4], "host != target")
-class X86_64(MigrationTest):
-    """
-    :avocado: tags=arch:x86_64
-    :avocado: tags=machine:pc
-    :avocado: tags=cpu:qemu64
-    """
-
-    def test_migration_with_tcp_localhost(self):
-        self.migration_with_tcp_localhost()
-
-    def test_migration_with_unix(self):
-        self.migration_with_unix()
-
+    @skipIfMissingCommands('nc')
     def test_migration_with_exec(self):
-        self.migration_with_exec()
-
-
-@skipUnless('ppc64le' in os.uname()[4], "host != target")
-class PPC64(MigrationTest):
-    """
-    :avocado: tags=arch:ppc64
-    :avocado: tags=machine:pseries
-    """
-
-    def test_migration_with_tcp_localhost(self):
-        self.migration_with_tcp_localhost()
-
-    def test_migration_with_unix(self):
-        self.migration_with_unix()
-
-    def test_migration_with_exec(self):
-        self.migration_with_exec()
+        """The test works for both netcat-traditional and netcat-openbsd packages."""
+        with Ports() as ports:
+            free_port = self._get_free_port(ports)
+            dest_uri = 'exec:nc -l localhost %u' % free_port
+            src_uri = 'exec:nc localhost %u' % free_port
+            self.do_migrate(dest_uri, src_uri)
+
+if __name__ == '__main__':
+    QemuSystemTest.main()