diff mbox

[v3,08/16] boot-order-test: New; covering just PC for now

Message ID 1371208516-7857-9-git-send-email-armbru@redhat.com
State New
Headers show

Commit Message

Markus Armbruster June 14, 2013, 11:15 a.m. UTC
Signed-off-by: Markus Armbruster <armbru@redhat.com>
---
 tests/Makefile          |  2 ++
 tests/boot-order-test.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 70 insertions(+)
 create mode 100644 tests/boot-order-test.c

Comments

Anthony Liguori June 14, 2013, 1:48 p.m. UTC | #1
Markus Armbruster <armbru@redhat.com> writes:

> Signed-off-by: Markus Armbruster <armbru@redhat.com>
> ---
>  tests/Makefile          |  2 ++
>  tests/boot-order-test.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 70 insertions(+)
>  create mode 100644 tests/boot-order-test.c
>
> diff --git a/tests/Makefile b/tests/Makefile
> index c107489..394e029 100644
> --- a/tests/Makefile
> +++ b/tests/Makefile
> @@ -54,6 +54,7 @@ gcov-files-i386-y = hw/fdc.c
>  check-qtest-i386-y += tests/ide-test$(EXESUF)
>  check-qtest-i386-y += tests/hd-geo-test$(EXESUF)
>  gcov-files-i386-y += hw/hd-geometry.c
> +check-qtest-i386-y += tests/boot-order-test$(EXESUF)
>  check-qtest-i386-y += tests/rtc-test$(EXESUF)
>  check-qtest-i386-y += tests/i440fx-test$(EXESUF)
>  check-qtest-i386-y += tests/fw_cfg-test$(EXESUF)
> @@ -130,6 +131,7 @@ tests/m48t59-test$(EXESUF): tests/m48t59-test.o
>  tests/fdc-test$(EXESUF): tests/fdc-test.o
>  tests/ide-test$(EXESUF): tests/ide-test.o $(libqos-pc-obj-y)
>  tests/hd-geo-test$(EXESUF): tests/hd-geo-test.o
> +tests/boot-order-test$(EXESUF): tests/boot-order-test.o
>  tests/tmp105-test$(EXESUF): tests/tmp105-test.o $(libqos-omap-obj-y)
>  tests/i440fx-test$(EXESUF): tests/i440fx-test.o $(libqos-pc-obj-y)
>  tests/fw_cfg-test$(EXESUF): tests/fw_cfg-test.o $(libqos-pc-obj-y)
> diff --git a/tests/boot-order-test.c b/tests/boot-order-test.c
> new file mode 100644
> index 0000000..2215710
> --- /dev/null
> +++ b/tests/boot-order-test.c
> @@ -0,0 +1,68 @@
> +/*
> + * Boot order test cases.
> + *
> + * Copyright (c) 2013 Red Hat Inc.
> + *
> + * Authors:
> + *  Markus Armbruster <armbru@redhat.com>,
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2 or later.
> + * See the COPYING file in the top-level directory.
> + */
> +
> +#include <glib.h>
> +#include "libqtest.h"
> +
> +static void test_pc_cmos_byte(int reg, int expected)
> +{
> +    int actual;
> +
> +    outb(0x70, reg);
> +    actual = inb(0x71);
> +    g_assert_cmphex(actual, ==, expected);
> +}
> +
> +static void test_pc_cmos(uint8_t boot1, uint8_t boot2)
> +{
> +    test_pc_cmos_byte(0x38, boot1);
> +    test_pc_cmos_byte(0x3d, boot2);
> +}
> +
> +static void test_pc_with_args(const char *test_args,
> +                              uint8_t boot1, uint8_t boot2,
> +                              uint8_t reboot1, uint8_t reboot2)
> +{
> +    char *args = g_strdup_printf("-nodefaults -display none %s", test_args);
> +
> +    qtest_start(args);
> +    test_pc_cmos(boot1, boot2);
> +    qmp("{ 'execute': 'system_reset' }");

I think this races.  I'd suggest doing a tight loop of this test and
running it a few thousand times to see if you can catch it.

qmp_system_reset() calls qemu_system_reset_requested() which stops all
CPUs but let's control fall back to the main loop which actually does
the device reset.

I think there's a tiny window where this command could return while the
reset routines have not been actually called yet.

Technically speaking, I think it's necessary to wait for a reset event
to know that the device model has been reset.

Regards,

Anthony Liguori

> +    test_pc_cmos(reboot1, reboot2);
> +    qtest_quit(global_qtest);
> +    g_free(args);
> +}
> +
> +static void test_pc_boot_order(void)
> +{
> +    test_pc_with_args("", 0x30, 0x12, 0x30, 0x12);
> +    test_pc_with_args("-no-fd-bootchk", 0x31, 0x12, 0x31, 0x12);
> +    test_pc_with_args("-boot c", 0, 0x02, 0, 0x02);
> +    test_pc_with_args("-boot nda", 0x10, 0x34, 0x10, 0x34);
> +    test_pc_with_args("-boot order=", 0, 0, 0, 0);
> +    test_pc_with_args("-boot order= -boot order=c", 0, 0x02, 0, 0x02);
> +    test_pc_with_args("-boot once=a", 0, 0x01, 0x30, 0x12);
> +    test_pc_with_args("-boot once=a -no-fd-bootchk", 0x01, 0x01, 0x31, 0x12);
> +    test_pc_with_args("-boot once=a,order=c", 0, 0x01, 0, 0x02);
> +    test_pc_with_args("-boot once=d -boot order=nda", 0, 0x03, 0x10, 0x34);
> +    test_pc_with_args("-boot once=a -boot once=b -boot once=c",
> +                      0, 0x02, 0x30, 0x12);
> +}
> +
> +int main(int argc, char *argv[])
> +{
> +    g_test_init(&argc, &argv, NULL);
> +
> +    qtest_add_func("boot-order/pc", test_pc_boot_order);
> +
> +    return g_test_run();
> +}
> -- 
> 1.7.11.7
Markus Armbruster June 18, 2013, 1:33 p.m. UTC | #2
Anthony Liguori <aliguori@us.ibm.com> writes:

> Markus Armbruster <armbru@redhat.com> writes:
>
>> Signed-off-by: Markus Armbruster <armbru@redhat.com>
>> ---
>>  tests/Makefile          |  2 ++
>>  tests/boot-order-test.c | 68
>> +++++++++++++++++++++++++++++++++++++++++++++++++
>>  2 files changed, 70 insertions(+)
>>  create mode 100644 tests/boot-order-test.c
>>
>> diff --git a/tests/Makefile b/tests/Makefile
>> index c107489..394e029 100644
>> --- a/tests/Makefile
>> +++ b/tests/Makefile
>> @@ -54,6 +54,7 @@ gcov-files-i386-y = hw/fdc.c
>>  check-qtest-i386-y += tests/ide-test$(EXESUF)
>>  check-qtest-i386-y += tests/hd-geo-test$(EXESUF)
>>  gcov-files-i386-y += hw/hd-geometry.c
>> +check-qtest-i386-y += tests/boot-order-test$(EXESUF)
>>  check-qtest-i386-y += tests/rtc-test$(EXESUF)
>>  check-qtest-i386-y += tests/i440fx-test$(EXESUF)
>>  check-qtest-i386-y += tests/fw_cfg-test$(EXESUF)
>> @@ -130,6 +131,7 @@ tests/m48t59-test$(EXESUF): tests/m48t59-test.o
>>  tests/fdc-test$(EXESUF): tests/fdc-test.o
>>  tests/ide-test$(EXESUF): tests/ide-test.o $(libqos-pc-obj-y)
>>  tests/hd-geo-test$(EXESUF): tests/hd-geo-test.o
>> +tests/boot-order-test$(EXESUF): tests/boot-order-test.o
>>  tests/tmp105-test$(EXESUF): tests/tmp105-test.o $(libqos-omap-obj-y)
>>  tests/i440fx-test$(EXESUF): tests/i440fx-test.o $(libqos-pc-obj-y)
>>  tests/fw_cfg-test$(EXESUF): tests/fw_cfg-test.o $(libqos-pc-obj-y)
>> diff --git a/tests/boot-order-test.c b/tests/boot-order-test.c
>> new file mode 100644
>> index 0000000..2215710
>> --- /dev/null
>> +++ b/tests/boot-order-test.c
>> @@ -0,0 +1,68 @@
>> +/*
>> + * Boot order test cases.
>> + *
>> + * Copyright (c) 2013 Red Hat Inc.
>> + *
>> + * Authors:
>> + *  Markus Armbruster <armbru@redhat.com>,
>> + *
>> + * This work is licensed under the terms of the GNU GPL, version 2 or later.
>> + * See the COPYING file in the top-level directory.
>> + */
>> +
>> +#include <glib.h>
>> +#include "libqtest.h"
>> +
>> +static void test_pc_cmos_byte(int reg, int expected)
>> +{
>> +    int actual;
>> +
>> +    outb(0x70, reg);
>> +    actual = inb(0x71);
>> +    g_assert_cmphex(actual, ==, expected);
>> +}
>> +
>> +static void test_pc_cmos(uint8_t boot1, uint8_t boot2)
>> +{
>> +    test_pc_cmos_byte(0x38, boot1);
>> +    test_pc_cmos_byte(0x3d, boot2);
>> +}
>> +
>> +static void test_pc_with_args(const char *test_args,
>> +                              uint8_t boot1, uint8_t boot2,
>> +                              uint8_t reboot1, uint8_t reboot2)
>> +{
>> +    char *args = g_strdup_printf("-nodefaults -display none %s", test_args);
>> +
>> +    qtest_start(args);
>> +    test_pc_cmos(boot1, boot2);
>> +    qmp("{ 'execute': 'system_reset' }");
        test_pc_cmos(reboot1, reboot2);
>
> I think this races.  I'd suggest doing a tight loop of this test and
> running it a few thousand times to see if you can catch it.
>
> qmp_system_reset() calls qemu_system_reset_requested() which stops all
> CPUs but let's control fall back to the main loop which actually does
> the device reset.
>
> I think there's a tiny window where this command could return while the
> reset routines have not been actually called yet.
>
> Technically speaking, I think it's necessary to wait for a reset event
> to know that the device model has been reset.

Hmm.

First attempt to "win" this race: tight loop around test_a_boot_order(),
i.e. the complete test.  Failed because libqtest leaks two file
descriptors and some memory per iteration.  With that fixed (patch
coming), I still couldn't make the test fail in >75,000 runs on two
otherwise pretty much unloaded cores.

Second attempt: tight loop around just

    qmp("{ 'execute': 'system_reset' }");
    actual = read_boot_order();
    g_assert_cmphex(actual, ==, expected_reboot);

Still no luck with x86, but "success" with ppc.

Waiting for the event RESET is safe.  But doing that right involves
quite some infrastructure work.  All we have now is qtest_qmpv(), which
sends the command, then reads QMP output character by character until it
got a complete object.  Normally, that's the QMP command response.  But
it could be an event.  Racy all by itself, even without my "help" :)

Oh, and it doesn't know about strings, it just counts curlies.  If the
output has unmatched curlies in strings...  I wonder how this code ever
made it past review ;-P

The proper solution is real QMP support in libqtest.  Unfortunately,
that's not something I can do right now.

fdc-test.c uses qmp("") to ignore an expected event.  If I put a similar
hack into boot-order-test.c, ppc survives >500,000 iterations.  Good
enough to get this test in?

[...]
Andreas Färber June 18, 2013, 2:13 p.m. UTC | #3
Am 18.06.2013 15:33, schrieb Markus Armbruster:
> Anthony Liguori <aliguori@us.ibm.com> writes:
> 
>> Markus Armbruster <armbru@redhat.com> writes:
>>
>>> Signed-off-by: Markus Armbruster <armbru@redhat.com>
>>> ---
>>>  tests/Makefile          |  2 ++
>>>  tests/boot-order-test.c | 68
>>> +++++++++++++++++++++++++++++++++++++++++++++++++
>>>  2 files changed, 70 insertions(+)
>>>  create mode 100644 tests/boot-order-test.c
>>>
>>> diff --git a/tests/Makefile b/tests/Makefile
>>> index c107489..394e029 100644
>>> --- a/tests/Makefile
>>> +++ b/tests/Makefile
>>> @@ -54,6 +54,7 @@ gcov-files-i386-y = hw/fdc.c
>>>  check-qtest-i386-y += tests/ide-test$(EXESUF)
>>>  check-qtest-i386-y += tests/hd-geo-test$(EXESUF)
>>>  gcov-files-i386-y += hw/hd-geometry.c
>>> +check-qtest-i386-y += tests/boot-order-test$(EXESUF)
>>>  check-qtest-i386-y += tests/rtc-test$(EXESUF)
>>>  check-qtest-i386-y += tests/i440fx-test$(EXESUF)
>>>  check-qtest-i386-y += tests/fw_cfg-test$(EXESUF)
>>> @@ -130,6 +131,7 @@ tests/m48t59-test$(EXESUF): tests/m48t59-test.o
>>>  tests/fdc-test$(EXESUF): tests/fdc-test.o
>>>  tests/ide-test$(EXESUF): tests/ide-test.o $(libqos-pc-obj-y)
>>>  tests/hd-geo-test$(EXESUF): tests/hd-geo-test.o
>>> +tests/boot-order-test$(EXESUF): tests/boot-order-test.o
>>>  tests/tmp105-test$(EXESUF): tests/tmp105-test.o $(libqos-omap-obj-y)
>>>  tests/i440fx-test$(EXESUF): tests/i440fx-test.o $(libqos-pc-obj-y)
>>>  tests/fw_cfg-test$(EXESUF): tests/fw_cfg-test.o $(libqos-pc-obj-y)
>>> diff --git a/tests/boot-order-test.c b/tests/boot-order-test.c
>>> new file mode 100644
>>> index 0000000..2215710
>>> --- /dev/null
>>> +++ b/tests/boot-order-test.c
>>> @@ -0,0 +1,68 @@
>>> +/*
>>> + * Boot order test cases.
>>> + *
>>> + * Copyright (c) 2013 Red Hat Inc.
>>> + *
>>> + * Authors:
>>> + *  Markus Armbruster <armbru@redhat.com>,
>>> + *
>>> + * This work is licensed under the terms of the GNU GPL, version 2 or later.
>>> + * See the COPYING file in the top-level directory.
>>> + */
>>> +
>>> +#include <glib.h>
>>> +#include "libqtest.h"
>>> +
>>> +static void test_pc_cmos_byte(int reg, int expected)
>>> +{
>>> +    int actual;
>>> +
>>> +    outb(0x70, reg);
>>> +    actual = inb(0x71);
>>> +    g_assert_cmphex(actual, ==, expected);
>>> +}
>>> +
>>> +static void test_pc_cmos(uint8_t boot1, uint8_t boot2)
>>> +{
>>> +    test_pc_cmos_byte(0x38, boot1);
>>> +    test_pc_cmos_byte(0x3d, boot2);
>>> +}
>>> +
>>> +static void test_pc_with_args(const char *test_args,
>>> +                              uint8_t boot1, uint8_t boot2,
>>> +                              uint8_t reboot1, uint8_t reboot2)
>>> +{
>>> +    char *args = g_strdup_printf("-nodefaults -display none %s", test_args);
>>> +
>>> +    qtest_start(args);
>>> +    test_pc_cmos(boot1, boot2);
>>> +    qmp("{ 'execute': 'system_reset' }");
>         test_pc_cmos(reboot1, reboot2);
>>
>> I think this races.  I'd suggest doing a tight loop of this test and
>> running it a few thousand times to see if you can catch it.
>>
>> qmp_system_reset() calls qemu_system_reset_requested() which stops all
>> CPUs but let's control fall back to the main loop which actually does
>> the device reset.
>>
>> I think there's a tiny window where this command could return while the
>> reset routines have not been actually called yet.
>>
>> Technically speaking, I think it's necessary to wait for a reset event
>> to know that the device model has been reset.
> 
> Hmm.
> 
> First attempt to "win" this race: tight loop around test_a_boot_order(),
> i.e. the complete test.  Failed because libqtest leaks two file
> descriptors and some memory per iteration.  With that fixed (patch
> coming), I still couldn't make the test fail in >75,000 runs on two
> otherwise pretty much unloaded cores.
> 
> Second attempt: tight loop around just
> 
>     qmp("{ 'execute': 'system_reset' }");
>     actual = read_boot_order();
>     g_assert_cmphex(actual, ==, expected_reboot);
> 
> Still no luck with x86, but "success" with ppc.
> 
> Waiting for the event RESET is safe.  But doing that right involves
> quite some infrastructure work.  All we have now is qtest_qmpv(), which
> sends the command, then reads QMP output character by character until it
> got a complete object.  Normally, that's the QMP command response.  But
> it could be an event.  Racy all by itself, even without my "help" :)
> 
> Oh, and it doesn't know about strings, it just counts curlies.  If the
> output has unmatched curlies in strings...  I wonder how this code ever
> made it past review ;-P
> 
> The proper solution is real QMP support in libqtest.  Unfortunately,
> that's not something I can do right now.

Didn't Michael or Jason have patches improving the qtest QMP support for
migration some months ago? At least they allowed to access the QMP
response in parsed form IIRC.

Andreas

> 
> fdc-test.c uses qmp("") to ignore an expected event.  If I put a similar
> hack into boot-order-test.c, ppc survives >500,000 iterations.  Good
> enough to get this test in?
> 
> [...]
>
Anthony Liguori June 18, 2013, 3:02 p.m. UTC | #4
Markus Armbruster <armbru@redhat.com> writes:

> Anthony Liguori <aliguori@us.ibm.com> writes:
>
>> Markus Armbruster <armbru@redhat.com> writes:
>>
>>> Signed-off-by: Markus Armbruster <armbru@redhat.com>
>>> ---
>>>  tests/Makefile          |  2 ++
>>>  tests/boot-order-test.c | 68
>>> +++++++++++++++++++++++++++++++++++++++++++++++++
>>>  2 files changed, 70 insertions(+)
>>>  create mode 100644 tests/boot-order-test.c
>>>
>>> diff --git a/tests/Makefile b/tests/Makefile
>>> index c107489..394e029 100644
>>> --- a/tests/Makefile
>>> +++ b/tests/Makefile
>>> @@ -54,6 +54,7 @@ gcov-files-i386-y = hw/fdc.c
>>>  check-qtest-i386-y += tests/ide-test$(EXESUF)
>>>  check-qtest-i386-y += tests/hd-geo-test$(EXESUF)
>>>  gcov-files-i386-y += hw/hd-geometry.c
>>> +check-qtest-i386-y += tests/boot-order-test$(EXESUF)
>>>  check-qtest-i386-y += tests/rtc-test$(EXESUF)
>>>  check-qtest-i386-y += tests/i440fx-test$(EXESUF)
>>>  check-qtest-i386-y += tests/fw_cfg-test$(EXESUF)
>>> @@ -130,6 +131,7 @@ tests/m48t59-test$(EXESUF): tests/m48t59-test.o
>>>  tests/fdc-test$(EXESUF): tests/fdc-test.o
>>>  tests/ide-test$(EXESUF): tests/ide-test.o $(libqos-pc-obj-y)
>>>  tests/hd-geo-test$(EXESUF): tests/hd-geo-test.o
>>> +tests/boot-order-test$(EXESUF): tests/boot-order-test.o
>>>  tests/tmp105-test$(EXESUF): tests/tmp105-test.o $(libqos-omap-obj-y)
>>>  tests/i440fx-test$(EXESUF): tests/i440fx-test.o $(libqos-pc-obj-y)
>>>  tests/fw_cfg-test$(EXESUF): tests/fw_cfg-test.o $(libqos-pc-obj-y)
>>> diff --git a/tests/boot-order-test.c b/tests/boot-order-test.c
>>> new file mode 100644
>>> index 0000000..2215710
>>> --- /dev/null
>>> +++ b/tests/boot-order-test.c
>>> @@ -0,0 +1,68 @@
>>> +/*
>>> + * Boot order test cases.
>>> + *
>>> + * Copyright (c) 2013 Red Hat Inc.
>>> + *
>>> + * Authors:
>>> + *  Markus Armbruster <armbru@redhat.com>,
>>> + *
>>> + * This work is licensed under the terms of the GNU GPL, version 2 or later.
>>> + * See the COPYING file in the top-level directory.
>>> + */
>>> +
>>> +#include <glib.h>
>>> +#include "libqtest.h"
>>> +
>>> +static void test_pc_cmos_byte(int reg, int expected)
>>> +{
>>> +    int actual;
>>> +
>>> +    outb(0x70, reg);
>>> +    actual = inb(0x71);
>>> +    g_assert_cmphex(actual, ==, expected);
>>> +}
>>> +
>>> +static void test_pc_cmos(uint8_t boot1, uint8_t boot2)
>>> +{
>>> +    test_pc_cmos_byte(0x38, boot1);
>>> +    test_pc_cmos_byte(0x3d, boot2);
>>> +}
>>> +
>>> +static void test_pc_with_args(const char *test_args,
>>> +                              uint8_t boot1, uint8_t boot2,
>>> +                              uint8_t reboot1, uint8_t reboot2)
>>> +{
>>> +    char *args = g_strdup_printf("-nodefaults -display none %s", test_args);
>>> +
>>> +    qtest_start(args);
>>> +    test_pc_cmos(boot1, boot2);
>>> +    qmp("{ 'execute': 'system_reset' }");
>         test_pc_cmos(reboot1, reboot2);
>>
>> I think this races.  I'd suggest doing a tight loop of this test and
>> running it a few thousand times to see if you can catch it.
>>
>> qmp_system_reset() calls qemu_system_reset_requested() which stops all
>> CPUs but let's control fall back to the main loop which actually does
>> the device reset.
>>
>> I think there's a tiny window where this command could return while the
>> reset routines have not been actually called yet.
>>
>> Technically speaking, I think it's necessary to wait for a reset event
>> to know that the device model has been reset.
>
> Hmm.
>
> First attempt to "win" this race: tight loop around test_a_boot_order(),
> i.e. the complete test.  Failed because libqtest leaks two file
> descriptors and some memory per iteration.  With that fixed (patch
> coming), I still couldn't make the test fail in >75,000 runs on two
> otherwise pretty much unloaded cores.
>
> Second attempt: tight loop around just
>
>     qmp("{ 'execute': 'system_reset' }");
>     actual = read_boot_order();
>     g_assert_cmphex(actual, ==, expected_reboot);
>
> Still no luck with x86, but "success" with ppc.
>
> Waiting for the event RESET is safe.  But doing that right involves
> quite some infrastructure work.  All we have now is qtest_qmpv(), which
> sends the command, then reads QMP output character by character until it
> got a complete object.  Normally, that's the QMP command response.  But
> it could be an event.  Racy all by itself, even without my "help" :)
>
> Oh, and it doesn't know about strings, it just counts curlies.  If the
> output has unmatched curlies in strings...  I wonder how this code ever
> made it past review ;-P
>
> The proper solution is real QMP support in libqtest.  Unfortunately,
> that's not something I can do right now.
>
> fdc-test.c uses qmp("") to ignore an expected event.  If I put a similar
> hack into boot-order-test.c, ppc survives >500,000 iterations.  Good
> enough to get this test in?

With a very big comment stating what's happening.

Regards,

Anthony Liguori

>
> [...]
diff mbox

Patch

diff --git a/tests/Makefile b/tests/Makefile
index c107489..394e029 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -54,6 +54,7 @@  gcov-files-i386-y = hw/fdc.c
 check-qtest-i386-y += tests/ide-test$(EXESUF)
 check-qtest-i386-y += tests/hd-geo-test$(EXESUF)
 gcov-files-i386-y += hw/hd-geometry.c
+check-qtest-i386-y += tests/boot-order-test$(EXESUF)
 check-qtest-i386-y += tests/rtc-test$(EXESUF)
 check-qtest-i386-y += tests/i440fx-test$(EXESUF)
 check-qtest-i386-y += tests/fw_cfg-test$(EXESUF)
@@ -130,6 +131,7 @@  tests/m48t59-test$(EXESUF): tests/m48t59-test.o
 tests/fdc-test$(EXESUF): tests/fdc-test.o
 tests/ide-test$(EXESUF): tests/ide-test.o $(libqos-pc-obj-y)
 tests/hd-geo-test$(EXESUF): tests/hd-geo-test.o
+tests/boot-order-test$(EXESUF): tests/boot-order-test.o
 tests/tmp105-test$(EXESUF): tests/tmp105-test.o $(libqos-omap-obj-y)
 tests/i440fx-test$(EXESUF): tests/i440fx-test.o $(libqos-pc-obj-y)
 tests/fw_cfg-test$(EXESUF): tests/fw_cfg-test.o $(libqos-pc-obj-y)
diff --git a/tests/boot-order-test.c b/tests/boot-order-test.c
new file mode 100644
index 0000000..2215710
--- /dev/null
+++ b/tests/boot-order-test.c
@@ -0,0 +1,68 @@ 
+/*
+ * Boot order test cases.
+ *
+ * Copyright (c) 2013 Red Hat Inc.
+ *
+ * Authors:
+ *  Markus Armbruster <armbru@redhat.com>,
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include <glib.h>
+#include "libqtest.h"
+
+static void test_pc_cmos_byte(int reg, int expected)
+{
+    int actual;
+
+    outb(0x70, reg);
+    actual = inb(0x71);
+    g_assert_cmphex(actual, ==, expected);
+}
+
+static void test_pc_cmos(uint8_t boot1, uint8_t boot2)
+{
+    test_pc_cmos_byte(0x38, boot1);
+    test_pc_cmos_byte(0x3d, boot2);
+}
+
+static void test_pc_with_args(const char *test_args,
+                              uint8_t boot1, uint8_t boot2,
+                              uint8_t reboot1, uint8_t reboot2)
+{
+    char *args = g_strdup_printf("-nodefaults -display none %s", test_args);
+
+    qtest_start(args);
+    test_pc_cmos(boot1, boot2);
+    qmp("{ 'execute': 'system_reset' }");
+    test_pc_cmos(reboot1, reboot2);
+    qtest_quit(global_qtest);
+    g_free(args);
+}
+
+static void test_pc_boot_order(void)
+{
+    test_pc_with_args("", 0x30, 0x12, 0x30, 0x12);
+    test_pc_with_args("-no-fd-bootchk", 0x31, 0x12, 0x31, 0x12);
+    test_pc_with_args("-boot c", 0, 0x02, 0, 0x02);
+    test_pc_with_args("-boot nda", 0x10, 0x34, 0x10, 0x34);
+    test_pc_with_args("-boot order=", 0, 0, 0, 0);
+    test_pc_with_args("-boot order= -boot order=c", 0, 0x02, 0, 0x02);
+    test_pc_with_args("-boot once=a", 0, 0x01, 0x30, 0x12);
+    test_pc_with_args("-boot once=a -no-fd-bootchk", 0x01, 0x01, 0x31, 0x12);
+    test_pc_with_args("-boot once=a,order=c", 0, 0x01, 0, 0x02);
+    test_pc_with_args("-boot once=d -boot order=nda", 0, 0x03, 0x10, 0x34);
+    test_pc_with_args("-boot once=a -boot once=b -boot once=c",
+                      0, 0x02, 0x30, 0x12);
+}
+
+int main(int argc, char *argv[])
+{
+    g_test_init(&argc, &argv, NULL);
+
+    qtest_add_func("boot-order/pc", test_pc_boot_order);
+
+    return g_test_run();
+}