expo: Support building an expo from a description file

The only way to create an expo at present is by calling the functions to
create each object. It is useful to have more data-driven approach, where
the objects can be specified in a suitable file format and created from
that. This makes testing easier as well.

Add support for describing an expo in a devicetree node. This allows more
complex tests to be set up, as well as providing an easier format for
users. It also provides a better basis for the upcoming configuration
editor.

Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Simon Glass 2023-06-01 10:23:01 -06:00 committed by Tom Rini
parent 7230fdb383
commit 82cafee133
9 changed files with 870 additions and 7 deletions

View file

@ -0,0 +1,64 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Expo definition for the configuration editor
*
* This used for testing building an expo from a data file. This devicetree
* provides a description of the objects to be created.
*/
#include <test/cedit-test.h>
&cedit {
dynamic-start = <ID_DYNAMIC_START>;
scenes {
main {
id = <ID_SCENE1>;
/* value refers to the matching id in /strings */
title-id = <ID_SCENE1_TITLE>;
/* simple string is used as it is */
prompt = "UP and DOWN to choose, ENTER to select";
/* defines a menu within the scene */
cpu-speed {
type = "menu";
id = <ID_CPU_SPEED>;
/*
* has both string and ID. The string is ignored
* if the ID is present and points to a string
*/
title = "CPU speed";
title-id = <ID_CPU_SPEED_TITLE>;
/* menu items as simple strings */
item-label = "2 GHz", "2.5 GHz", "3 GHz";
/* IDs for the menu items */
item-id = <ID_CPU_SPEED_1 ID_CPU_SPEED_2
ID_CPU_SPEED_3>;
};
power-loss {
type = "menu";
id = <ID_POWER_LOSS>;
title = "AC Power";
item-label = "Always Off", "Always On",
"Memory";
item-id = <ID_AC_OFF ID_AC_ON ID_AC_MEMORY>;
};
};
};
strings {
title {
id = <ID_SCENE1_TITLE>;
value = "Test Configuration";
value-es = "configuración de prueba";
};
};
};

View file

@ -141,6 +141,9 @@
};
};
cedit: cedit {
};
fuzzing-engine {
compatible = "sandbox,fuzzing-engine";
};
@ -1830,3 +1833,5 @@
#ifdef CONFIG_SANDBOX_VPL
#include "sandbox_vpl.dtsi"
#endif
#include "cedit.dtsi"

View file

@ -50,7 +50,7 @@ ifdef CONFIG_SPL_BUILD
obj-$(CONFIG_SPL_LOAD_FIT) += common_fit.o
endif
obj-$(CONFIG_$(SPL_TPL_)EXPO) += expo.o scene.o scene_menu.o
obj-$(CONFIG_$(SPL_TPL_)EXPO) += expo.o scene.o scene_menu.o expo_build.o
obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE) += vbe.o
obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_REQUEST) += vbe_request.o

View file

@ -58,6 +58,7 @@ void expo_destroy(struct expo *exp)
uint resolve_id(struct expo *exp, uint id)
{
log_debug("resolve id %d\n", id);
if (!id)
id = exp->next_id++;
else if (id >= exp->next_id)

400
boot/expo_build.c Normal file
View file

@ -0,0 +1,400 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Building an expo from an FDT description
*
* Copyright 2022 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#define LOG_CATEGORY LOGC_EXPO
#include <common.h>
#include <expo.h>
#include <fdtdec.h>
#include <log.h>
#include <malloc.h>
#include <dm/ofnode.h>
#include <linux/libfdt.h>
/**
* struct build_info - Information to use when building
*
* @str_for_id: String for each ID in use, NULL if empty. The string is NULL
* if there is nothing for this ID. Since ID 0 is never used, the first
* element of this array is always NULL
* @str_count: Number of entries in @str_for_id
*/
struct build_info {
const char **str_for_id;
int str_count;
};
/**
* add_txt_str - Add a string or lookup its ID, then add to expo
*
* @info: Build information
* @node: Node describing scene
* @scn: Scene to add to
* @find_name: Name to look for (e.g. "title"). This will find a property called
* "title" if it exists, else will look up the string for "title-id"
* Return: ID of added string, or -ve on error
*/
int add_txt_str(struct build_info *info, ofnode node, struct scene *scn,
const char *find_name, uint obj_id)
{
const char *text;
uint str_id;
int ret;
text = ofnode_read_string(node, find_name);
if (!text) {
char name[40];
u32 id;
snprintf(name, sizeof(name), "%s-id", find_name);
ret = ofnode_read_u32(node, name, &id);
if (ret)
return log_msg_ret("id", -EINVAL);
if (id >= info->str_count)
return log_msg_ret("id", -E2BIG);
text = info->str_for_id[id];
if (!text)
return log_msg_ret("id", -EINVAL);
}
ret = expo_str(scn->expo, find_name, 0, text);
if (ret < 0)
return log_msg_ret("add", ret);
str_id = ret;
ret = scene_txt_str(scn, find_name, obj_id, str_id, text, NULL);
if (ret < 0)
return log_msg_ret("add", ret);
return ret;
}
/**
* add_txt_str_list - Add a list string or lookup its ID, then add to expo
*
* @info: Build information
* @node: Node describing scene
* @scn: Scene to add to
* @find_name: Name to look for (e.g. "title"). This will find a string-list
* property called "title" if it exists, else will look up the string in the
* "title-id" string list.
* Return: ID of added string, or -ve on error
*/
int add_txt_str_list(struct build_info *info, ofnode node, struct scene *scn,
const char *find_name, int index, uint obj_id)
{
const char *text;
uint str_id;
int ret;
ret = ofnode_read_string_index(node, find_name, index, &text);
if (ret) {
char name[40];
u32 id;
snprintf(name, sizeof(name), "%s-id", find_name);
ret = ofnode_read_u32_index(node, name, index, &id);
if (ret)
return log_msg_ret("id", -ENOENT);
if (id >= info->str_count)
return log_msg_ret("id", -E2BIG);
text = info->str_for_id[id];
if (!text)
return log_msg_ret("id", -EINVAL);
}
ret = expo_str(scn->expo, find_name, 0, text);
if (ret < 0)
return log_msg_ret("add", ret);
str_id = ret;
ret = scene_txt_str(scn, find_name, obj_id, str_id, text, NULL);
if (ret < 0)
return log_msg_ret("add", ret);
return ret;
}
/*
* build_element() - Handle creating a text object from a label
*
* Look up a property called @label or @label-id and create a string for it
*/
int build_element(void *ldtb, int node, const char *label)
{
return 0;
}
/**
* read_strings() - Read in the list of strings
*
* Read the strings into an ID-indexed list, so they can be used for building
* an expo. The strings are in a /strings node and each has its own subnode
* containing the ID and the string itself:
*
* example {
* id = <123>;
* value = "This is a test";
* };
*
* Future work may add support for unicode and multiple languages
*
* @info: Build information
* @root: Root node to read from
* Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
* error
*/
static int read_strings(struct build_info *info, ofnode root)
{
ofnode strings, node;
strings = ofnode_find_subnode(root, "strings");
if (!ofnode_valid(strings))
return log_msg_ret("str", -EINVAL);
ofnode_for_each_subnode(node, strings) {
const char *val;
int ret;
u32 id;
ret = ofnode_read_u32(node, "id", &id);
if (ret)
return log_msg_ret("id", -EINVAL);
val = ofnode_read_string(node, "value");
if (!val)
return log_msg_ret("val", -EINVAL);
if (id >= info->str_count) {
int new_count = info->str_count + 20;
void *new_arr;
new_arr = realloc(info->str_for_id,
new_count * sizeof(char *));
if (!new_arr)
return log_msg_ret("id", -ENOMEM);
memset(new_arr + info->str_count, '\0',
(new_count - info->str_count) * sizeof(char *));
info->str_for_id = new_arr;
info->str_count = new_count;
}
info->str_for_id[id] = val;
}
return 0;
}
/**
* list_strings() - List the available strings with their IDs
*
* @info: Build information
*/
static void list_strings(struct build_info *info)
{
int i;
for (i = 0; i < info->str_count; i++) {
if (info->str_for_id[i])
printf("%3d %s\n", i, info->str_for_id[i]);
}
}
/**
* menu_build() - Build a menu and add it to a scene
*
* See doc/developer/expo.rst for a description of the format
*
* @info: Build information
* @node: Node containing the menu description
* @scn: Scene to add the menu to
* Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
* error, -ENOENT if there is a references to a non-existent string
*/
static int menu_build(struct build_info *info, ofnode node, struct scene *scn)
{
struct scene_obj_menu *menu;
uint title_id, menu_id;
const u32 *item_ids;
int ret, size, i;
const char *name;
u32 id;
name = ofnode_get_name(node);
ret = ofnode_read_u32(node, "id", &id);
if (ret)
return log_msg_ret("id", -EINVAL);
ret = scene_menu(scn, name, id, &menu);
if (ret < 0)
return log_msg_ret("men", ret);
menu_id = ret;
/* Set the title */
ret = add_txt_str(info, node, scn, "title", 0);
if (ret < 0)
return log_msg_ret("tit", ret);
title_id = ret;
ret = scene_menu_set_title(scn, menu_id, title_id);
item_ids = ofnode_read_prop(node, "item-id", &size);
if (!item_ids)
return log_msg_ret("itm", -EINVAL);
if (!size || size % sizeof(u32))
return log_msg_ret("isz", -EINVAL);
size /= sizeof(u32);
for (i = 0; i < size; i++) {
struct scene_menitem *item;
uint label, key, desc;
ret = add_txt_str_list(info, node, scn, "item-label", i, 0);
if (ret < 0 && ret != -ENOENT)
return log_msg_ret("lab", ret);
label = max(0, ret);
ret = add_txt_str_list(info, node, scn, "key-label", i, 0);
if (ret < 0 && ret != -ENOENT)
return log_msg_ret("key", ret);
key = max(0, ret);
ret = add_txt_str_list(info, node, scn, "desc-label", i, 0);
if (ret < 0 && ret != -ENOENT)
return log_msg_ret("lab", ret);
desc = max(0, ret);
ret = scene_menuitem(scn, menu_id, simple_xtoa(i),
fdt32_to_cpu(item_ids[i]), key, label,
desc, 0, 0, &item);
if (ret < 0)
return log_msg_ret("mi", ret);
}
return 0;
}
/**
* menu_build() - Build an expo object and add it to a scene
*
* See doc/developer/expo.rst for a description of the format
*
* @info: Build information
* @node: Node containing the object description
* @scn: Scene to add the object to
* Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
* error, -ENOENT if there is a references to a non-existent string
*/
static int obj_build(struct build_info *info, ofnode node, struct scene *scn)
{
const char *type;
u32 id;
int ret;
log_debug("- object %s\n", ofnode_get_name(node));
ret = ofnode_read_u32(node, "id", &id);
if (ret)
return log_msg_ret("id", -EINVAL);
type = ofnode_read_string(node, "type");
if (!type)
return log_msg_ret("typ", -EINVAL);
if (!strcmp("menu", type))
ret = menu_build(info, node, scn);
else
ret = -EINVAL;
if (ret)
return log_msg_ret("bld", ret);
return 0;
}
/**
* scene_build() - Build a scene and all its objects
*
* See doc/developer/expo.rst for a description of the format
*
* @info: Build information
* @node: Node containing the scene description
* @scn: Scene to add the object to
* Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
* error, -ENOENT if there is a references to a non-existent string
*/
static int scene_build(struct build_info *info, ofnode scn_node,
struct expo *exp)
{
const char *name;
struct scene *scn;
uint id, title_id;
ofnode node;
int ret;
name = ofnode_get_name(scn_node);
log_debug("Building scene %s\n", name);
ret = ofnode_read_u32(scn_node, "id", &id);
if (ret)
return log_msg_ret("id", -EINVAL);
ret = scene_new(exp, name, id, &scn);
if (ret < 0)
return log_msg_ret("scn", ret);
ret = add_txt_str(info, scn_node, scn, "title", 0);
if (ret < 0)
return log_msg_ret("tit", ret);
title_id = ret;
scene_title_set(scn, title_id);
ret = add_txt_str(info, scn_node, scn, "prompt", 0);
if (ret < 0)
return log_msg_ret("pr", ret);
ofnode_for_each_subnode(node, scn_node) {
ret = obj_build(info, node, scn);
if (ret < 0)
return log_msg_ret("mit", ret);
}
return 0;
}
int expo_build(ofnode root, struct expo **expp)
{
struct build_info info;
ofnode scenes, node;
struct expo *exp;
u32 dyn_start;
int ret;
memset(&info, '\0', sizeof(info));
ret = read_strings(&info, root);
if (ret)
return log_msg_ret("str", ret);
list_strings(&info);
ret = expo_new("name", NULL, &exp);
if (ret)
return log_msg_ret("exp", ret);
if (!ofnode_read_u32(root, "dynamic-start", &dyn_start))
expo_set_dynamic_start(exp, dyn_start);
scenes = ofnode_find_subnode(root, "scenes");
if (!ofnode_valid(scenes))
return log_msg_ret("sno", -EINVAL);
ofnode_for_each_subnode(node, scenes) {
ret = scene_build(&info, node, exp);
if (ret < 0)
return log_msg_ret("scn", ret);
}
*expp = exp;
return 0;
}

View file

@ -100,10 +100,13 @@ objects first, then create the menu item, passing in the relevant IDs.
Creating an expo
----------------
To create an expo, use `expo_new()` followed by `scene_new()` to create a scene.
Then add objects to the scene, using functions like `scene_txt_str()` and
`scene_menu()`. For every menu item, add text and image objects, then create
the menu item with `scene_menuitem()`, referring to those objects.
To create an expo programmatically, use `expo_new()` followed by `scene_new()`
to create a scene. Then add objects to the scene, using functions like
`scene_txt_str()` and `scene_menu()`. For every menu item, add text and image
objects, then create the menu item with `scene_menuitem()`, referring to those
objects.
To create an expo using a description file, see :ref:`expo_format` below.
Layout
------
@ -168,6 +171,273 @@ menu-inset
menuitem-gap-y
Number of pixels between menu items
.. _expo_format:
Pop-up mode
-----------
Expos support two modes. The simple mode is used for selecting from a single
menu, e.g. when choosing with OS to boot. In this mode the menu items are shown
in a list (label, > pointer, key and description) and can be chosen using arrow
keys and enter::
U-Boot Boot Menu
UP and DOWN to choose, ENTER to select
mmc1 > 0 Fedora-Workstation-armhfp-31-1.9
mmc3 1 Armbian
The popup mode allows multiple menus to be present in a scene. Each is shown
just as its title and label, as with the `CPU Speed` and `AC Power` menus here::
Test Configuration
CPU Speed <2 GHz> (highlighted)
AC Power Always Off
UP and DOWN to choose, ENTER to select
Expo Format
-----------
It can be tedious to create a complex expo using code. Expo supports a
data-driven approach, where the expo description is in a devicetree file. This
makes it easier and faster to create and edit the description. An expo builder
is provided to convert this format into an expo structure.
Layout of the expo scenes is handled automatically, based on a set of simple
rules.
Top-level node
~~~~~~~~~~~~~~
The top-level node has the following properties:
dynamic-start
type: u32, optional
Specifies the start of the dynamically allocated objects. This results in
a call to expo_set_dynamic_start().
The top-level node has the following subnodes:
scenes
Specifies the scenes in the expo, each one being a subnode
strings
Specifies the strings in the expo, each one being a subnode
`scenes` node
~~~~~~~~~~~~~
Contains a list of scene subnodes. The name of each subnode is passed as the
name to `scene_new()`.
`strings` node
~~~~~~~~~~~~~~
Contains a list of string subnodes. The name of each subnode is ignored.
`strings` subnodes
~~~~~~~~~~~~~~~~~~
Each subnode defines a string which can be used by scenes and objects. Each
string has an ID number which is used to refer to it.
The `strings` subnodes have the following properties:
id
type: u32, required
Specifies the ID number for the string.
value:
type: string, required
Specifies the string text. For now only a single value is supported. Future
work may add support for multiple languages by using a value for each
language.
Scene nodes (`scenes` subnodes)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Each subnode of the `scenes` node contains a scene description.
Most properties can use either a string or a string ID. For example, a `title`
property can be used to provide the title for a menu; alternatively a `title-id`
property can provide the string ID of the title. If both are present, the
ID takes preference, except that if a string with that ID does not exist, it
falls back to using the string from the property (`title` in this example). The
description below shows these are alternative properties with the same
description.
The scene nodes have the following properties:
id
type: u32, required
Specifies the ID number for the string.
title / title-id
type: string / u32, required
Specifies the title of the scene. This is shown at the top of the scene.
prompt / prompt-id
type: string / u32, required
Specifies a prompt for the scene. This is shown at the bottom of the scene.
The scene nodes have a subnode for each object in the scene.
Object nodes
~~~~~~~~~~~~
The object-node name is used as the name of the object, e.g. when calling
`scene_menu()` to create a menu.
Object nodes have the following common properties:
type
type: string, required
Specifies the type of the object. Valid types are:
"menu"
Menu containing items which can be selected by the user
id
type: u32, required
Specifies the ID of the object. This is used when referring to the object.
Menu nodes have the following additional properties:
title / title-id
type: string / u32, required
Specifies the title of the menu. This is shown to the left of the area for
this menu.
item-id
type: u32 list, required
Specifies the ID for each menu item. These are used for checking which item
has been selected.
item-label / item-label-id
type: string list / u32 list, required
Specifies the label for each item in the menu. These are shown to the user.
In 'popup' mode these form the items in the menu.
key-label / key-label-id
type: string list / u32 list, optional
Specifies the key for each item in the menu. These are currently only
intended for use in simple mode.
desc-label / desc-label-id
type: string list / u32 list, optional
Specifies the description for each item in the menu. These are currently
only intended for use in simple mode.
Expo layout
~~~~~~~~~~~
The `expo_arrange()` function can be called to arrange the expo objects in a
suitable manner. For each scene it puts the title at the top, the prompt at the
bottom and the objects in order from top to bottom.
Expo format example
~~~~~~~~~~~~~~~~~~~
This example shows an expo with a single scene consisting of two menus. The
scene title is specified using a string from the strings table, but all other
strings are provided inline in the nodes where they are used.
::
#define ID_PROMPT 1
#define ID_SCENE1 2
#define ID_SCENE1_TITLE 3
#define ID_CPU_SPEED 4
#define ID_CPU_SPEED_TITLE 5
#define ID_CPU_SPEED_1 6
#define ID_CPU_SPEED_2 7
#define ID_CPU_SPEED_3 8
#define ID_POWER_LOSS 9
#define ID_AC_OFF 10
#define ID_AC_ON 11
#define ID_AC_MEMORY 12
#define ID_DYNAMIC_START 13
&cedit {
dynamic-start = <ID_DYNAMIC_START>;
scenes {
main {
id = <ID_SCENE1>;
/* value refers to the matching id in /strings */
title-id = <ID_SCENE1_TITLE>;
/* simple string is used as it is */
prompt = "UP and DOWN to choose, ENTER to select";
/* defines a menu within the scene */
cpu-speed {
type = "menu";
id = <ID_CPU_SPEED>;
/*
* has both string and ID. The string is ignored
* if the ID is present and points to a string
*/
title = "CPU speed";
title-id = <ID_CPU_SPEED_TITLE>;
/* menu items as simple strings */
item-label = "2 GHz", "2.5 GHz", "3 GHz";
/* IDs for the menu items */
item-id = <ID_CPU_SPEED_1 ID_CPU_SPEED_2
ID_CPU_SPEED_3>;
};
power-loss {
type = "menu";
id = <ID_POWER_LOSS>;
title = "AC Power";
item-label = "Always Off", "Always On",
"Memory";
item-id = <ID_AC_OFF ID_AC_ON ID_AC_MEMORY>;
};
};
};
strings {
title {
id = <ID_SCENE1_TITLE>;
value = "Test Configuration";
value-es = "configuración de prueba";
};
};
};
API documentation
-----------------
@ -180,11 +450,10 @@ Future ideas
Some ideas for future work:
- Default menu item and a timeout
- Higher-level / automatic / more flexible layout of objects
- Image formats other than BMP
- Use of ANSI sequences to control a serial terminal
- Colour selection
- Better support for handling lots of settings, e.g. with radio/option widgets
- Support for more widgets, e.g. text, numeric, radio/option
- Mouse support
- Integrate Nuklear, NxWidgets or some other library for a richer UI
- Optimise rendering by only updating the display with changes since last render
@ -194,6 +463,7 @@ Some ideas for future work:
- Support both graphical and text menus at the same time on different devices
- Support unicode
- Support curses for proper serial-terminal menus
- Add support for large menus which need to scroll
.. Simon Glass <sjg@chromium.org>
.. 7-Oct-22

View file

@ -653,4 +653,18 @@ int expo_action_get(struct expo *exp, struct expo_action *act);
*/
int expo_apply_theme(struct expo *exp, ofnode node);
/**
* expo_build() - Build an expo from an FDT description
*
* Build a complete expo from a description in the provided devicetree.
*
* See doc/developer/expo.rst for a description of the format
*
* @root: Root node for expo description
* @expp: Returns the new expo
* Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
* error, -ENOENT if there is a references to a non-existent string
*/
int expo_build(ofnode root, struct expo **expp);
#endif /*__SCENE_H */

29
include/test/cedit-test.h Normal file
View file

@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Binding shared between cedit.dtsi and test/boot/expo.c
*
* Copyright 2023 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#ifndef __cedit_test_h
#define __cedit_test_h
#define ID_PROMPT 1
#define ID_SCENE1 2
#define ID_SCENE1_TITLE 3
#define ID_CPU_SPEED 4
#define ID_CPU_SPEED_TITLE 5
#define ID_CPU_SPEED_1 6
#define ID_CPU_SPEED_2 7
#define ID_CPU_SPEED_3 8
#define ID_POWER_LOSS 9
#define ID_AC_OFF 10
#define ID_AC_ON 11
#define ID_AC_MEMORY 12
#define ID_DYNAMIC_START 13
#endif

View file

@ -13,6 +13,7 @@
#include <test/suites.h>
#include <test/ut.h>
#include "bootstd_common.h"
#include <test/cedit-test.h>
#include "../../boot/scene_internal.h"
enum {
@ -588,3 +589,82 @@ static int expo_render_image(struct unit_test_state *uts)
return 0;
}
BOOTSTD_TEST(expo_render_image, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
/* Check building an expo from a devicetree description */
static int expo_test_build(struct unit_test_state *uts)
{
struct scene_obj_menu *menu;
struct scene_menitem *item;
struct scene_obj_txt *txt;
struct scene_obj *obj;
struct scene *scn;
struct expo *exp;
int count;
ofnode node;
node = ofnode_path("/cedit");
ut_assert(ofnode_valid(node));
ut_assertok(expo_build(node, &exp));
ut_asserteq_str("name", exp->name);
ut_asserteq(0, exp->scene_id);
ut_asserteq(ID_DYNAMIC_START + 20, exp->next_id);
ut_asserteq(false, exp->popup);
/* check the scene */
scn = expo_lookup_scene_id(exp, ID_SCENE1);
ut_assertnonnull(scn);
ut_asserteq_str("main", scn->name);
ut_asserteq(ID_SCENE1, scn->id);
ut_asserteq(ID_DYNAMIC_START + 1, scn->title_id);
ut_asserteq(0, scn->highlight_id);
/* check the title */
txt = scene_obj_find(scn, scn->title_id, SCENEOBJT_NONE);
ut_assertnonnull(txt);
obj = &txt->obj;
ut_asserteq_ptr(scn, obj->scene);
ut_asserteq_str("title", obj->name);
ut_asserteq(scn->title_id, obj->id);
ut_asserteq(SCENEOBJT_TEXT, obj->type);
ut_asserteq(0, obj->flags);
ut_asserteq_str("Test Configuration", expo_get_str(exp, txt->str_id));
/* check the menu */
menu = scene_obj_find(scn, ID_CPU_SPEED, SCENEOBJT_NONE);
obj = &menu->obj;
ut_asserteq_ptr(scn, obj->scene);
ut_asserteq_str("cpu-speed", obj->name);
ut_asserteq(ID_CPU_SPEED, obj->id);
ut_asserteq(SCENEOBJT_MENU, obj->type);
ut_asserteq(0, obj->flags);
txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_NONE);
ut_asserteq_str("CPU speed", expo_get_str(exp, txt->str_id));
ut_asserteq(0, menu->cur_item_id);
ut_asserteq(0, menu->pointer_id);
/* check the items */
item = list_first_entry(&menu->item_head, struct scene_menitem,
sibling);
ut_asserteq_str("00", item->name);
ut_asserteq(ID_CPU_SPEED_1, item->id);
ut_asserteq(0, item->key_id);
ut_asserteq(0, item->desc_id);
ut_asserteq(0, item->preview_id);
ut_asserteq(0, item->flags);
txt = scene_obj_find(scn, item->label_id, SCENEOBJT_NONE);
ut_asserteq_str("2 GHz", expo_get_str(exp, txt->str_id));
count = 0;
list_for_each_entry(item, &menu->item_head, sibling)
count++;
ut_asserteq(3, count);
expo_destroy(exp);
return 0;
}
BOOTSTD_TEST(expo_test_build, UT_TESTF_DM);