Browse Source

first version - and it works too!

pull/1/head
Lephenixnoir 1 year ago
commit
c32e91b7f4
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
  1. 16
      .gitignore
  2. 53
      CMakeLists.txt
  3. BIN
      assets-cg/icon-sel.png
  4. BIN
      assets-cg/icon-uns.png
  5. 21
      include/ft/all-tests.h
  6. 93
      include/ft/test.h
  7. 16
      include/ft/util.h
  8. 36
      include/ft/widgets/fbar.h
  9. 59
      include/ft/widgets/fbrowser.h
  10. 79
      include/ft/widgets/flist.h
  11. 110
      include/ft/widgets/gscreen.h
  12. 28
      src/ctype/trivialties.c
  13. 142
      src/main.c
  14. 168
      src/string/core.c
  15. 199
      src/string/memarray.c
  16. 96
      src/string/memarray.h
  17. 114
      src/test.c
  18. 187
      src/widgets/fbar.c
  19. 273
      src/widgets/fbrowser.c
  20. 217
      src/widgets/flist.c
  21. 188
      src/widgets/gscreen.c

16
.gitignore vendored

@ -0,0 +1,16 @@
# Build files
/build-fx
/build-cg
/*.g1a
/*.g3a
# Development files
/assets-cg/icon.xcf
# Python bytecode
__pycache__/
# Common IDE files
*.sublime-project
*.sublime-workspace
.vscode

53
CMakeLists.txt

@ -0,0 +1,53 @@
# Configure with [fxsdk build-fx] or [fxsdk build-cg], which provide the
# toolchain file and module path of the fxSDK
cmake_minimum_required(VERSION 3.15)
project(FxLibcTest)
include(GenerateG1A)
include(GenerateG3A)
include(Fxconv)
find_package(Gint 2.4 REQUIRED)
find_package(JustUI 1.0 REQUIRED)
# FXLIBC_INSTALL: Additional folders to read includes and libs from
set(SOURCES
src/main.c
src/test.c
src/widgets/fbar.c
src/widgets/fbrowser.c
src/widgets/flist.c
src/widgets/gscreen.c
# ctype
src/ctype/trivialties.c
# string
src/string/memarray.c
src/string/core.c
)
# fx-9860G-only assets and fx-CG-50-only assets
set(ASSETS_fx
)
set(ASSETS_cg
)
fxconv_declare_assets(${ASSETS} ${ASSETS_fx} ${ASSETS_cg} WITH_METADATA)
add_executable(fxlibctest ${SOURCES} ${ASSETS} ${ASSETS_${FXSDK_PLATFORM}})
target_compile_options(fxlibctest PRIVATE -Wall -Wextra -Os)
target_link_options(fxlibctest PRIVATE -Wl,-Map=map -Wl,--print-memory-usage)
target_include_directories(fxlibctest PRIVATE include/)
target_link_libraries(fxlibctest JustUI::JustUI Gint::Gint -lc)
foreach(FOLDER IN LISTS FXLIBC_INSTALL)
target_include_directories(fxlibctest PRIVATE "${FOLDER}/include")
target_link_directories(fxlibctest PRIVATE "${FOLDER}/lib")
endforeach()
if("${FXSDK_PLATFORM_LONG}" STREQUAL fx9860G)
generate_g1a(TARGET fxlibctest OUTPUT "FxLibcT.g1a"
NAME "FxLibc test" ICON assets-fx/icon.png)
elseif("${FXSDK_PLATFORM_LONG}" STREQUAL fxCG50)
generate_g3a(TARGET fxlibctest OUTPUT "FxLibcT.g3a"
NAME "FxLibc test" ICONS assets-cg/icon-uns.png assets-cg/icon-sel.png)
endif()

BIN
assets-cg/icon-sel.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
assets-cg/icon-uns.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

21
include/ft/all-tests.h

@ -0,0 +1,21 @@
//---
// ft.all-tests: List of all test structures defined by modules
//---
#ifndef _FT_ALL_TESTS_H_
#define _FT_ALL_TESTS_H_
#include <ft/test.h>
/* ctype */
extern ft_test ft_ctype_trivial_success;
extern ft_test ft_ctype_trivial_failure;
extern ft_test ft_ctype_trivial_empty;
/* string */
extern ft_test ft_string_memset;
extern ft_test ft_string_memcpy;
extern ft_test ft_string_memmove;
extern ft_test ft_string_memcmp;
#endif /* _FT_ALL_TESTS_H_ */

93
include/ft/test.h

@ -0,0 +1,93 @@
//---
// ft.test: Testing tools and status reports
//---
#ifndef _FT_TEST_H_
#define _FT_TEST_H_
#include <gint/display.h>
#include <justui/jwidget.h>
//---
// Test framework
//---
/* ft_test: A series of assertions grouped together in a single function */
typedef struct ft_test {
/* Test name */
char const *name;
/* Test function */
void (*function)(struct ft_test *t);
/* Optional visualization widget; called when the visualization is
requested. The widget can be interactive, but it must not accept EXIT or
MENU events. It is destroyed atfer use. */
jwidget *(*widget)(struct ft_test *test);
/* Test log; this can be displayed by a scrolling widget */
char *log;
/* Number of passed, skipped and failed assertions in the test */
uint16_t passed;
uint16_t skipped;
uint16_t failed;
} ft_test;
/* ft_test_init(): Init counters and logs for a test */
void ft_test_init(ft_test *test);
/* ft_test_run(): Reset and run a test */
void ft_test_run(ft_test *test);
/* ft_test_done(): Check whether a test is done */
bool ft_test_done(ft_test *test);
/* ft_assert(): Record an assertion in a test */
void ft_assert(ft_test *test, int expression,
char const *file, int line, char const *expression_str);
#define ft_assert(test, expression) \
ft_assert(test, expression, __FILE__, __LINE__, #expression)
/* ft_log(): Write some data in a test's log */
__attribute__((format(printf, 2, 3)))
void ft_log(ft_test *test, char const *format, ...);
//---
// Test lists (to organize by header)
//---
/* ft_list: A named list of tests with aggregating statistics */
typedef struct {
/* List name (header name) */
char const *name;
/* NULL-terminated list of children */
ft_test **children;
/* Number of passed, skipped and failed assertions in the children */
uint16_t passed;
uint16_t skipped;
uint16_t failed;
} ft_list;
/* ft_list_init(): Init counters and logs for a NULL-terminated test list */
void ft_list_init(ft_list *list);
/* ft_list_run(): Reset and run a list of tests (aggregates automatically) */
void ft_list_run(ft_list *list);
/* ft_list_aggregate(): Update list results based on the children's results */
void ft_list_aggregate(ft_list *l);
//---
// Utilities
//---
/* Colors for each test category */
#define FT_TEST_EMPTY C_RGB(20,20,20)
#define FT_TEST_PASSED C_RGB(6,25,8)
#define FT_TEST_SKIPPED C_RGB(31,20,9)
#define FT_TEST_PENDING C_RGB(15,23,27)
#define FT_TEST_FAILED C_RGB(31,10,7)
/* ft_test_color(): Color for the summary of a test */
int ft_test_color(ft_test const *t);
/* ft_list_color(): Color for the summary of a list of tests */
int ft_list_color(ft_list const *t);
#endif /* _FT_TEST_H_ */

16
include/ft/util.h

@ -0,0 +1,16 @@
//---
// fxlibtest.util: Random utilities
//---
#ifndef _FT_UTIL_H_
#define _FT_UTIL_H_
#ifdef FX9860G
#define _(fx,cg) (fx)
#endif
#ifdef FXCG50
#define _(fx, cg) (cg)
#endif
#endif /* _FT_UTIL_H_ */

36
include/ft/widgets/fbar.h

@ -0,0 +1,36 @@
//---
// ft.widgets.fbar: A colored bar summarizing test results
//---
#ifndef _FT_WIDGETS_FBAR_H_
#define _FT_WIDGETS_FBAR_H_
#include <justui/jwidget.h>
#include <ft/test.h>
/* fbar: A bar summarizing test results with colored sections
This widget is a simple display widget. It displays either a summary for a
list or a summary for a whole header set (list of lists).
The length of the pending section is based on the proportion of pending
tests. The length of the other sections is based on the number of assertions
in each category. */
typedef struct {
jwidget widget;
/* Current set of tests or set of lists (only one can be non-NULL) */
ft_test **tests;
ft_list *lists;
} fbar;
/* fbar_create(): Create an fbar with no data tied */
fbar *fbar_create(void *parent);
/* fbar_set_tests(): Show statistics for a list */
void fbar_set_tests(fbar *b, ft_test **tests);
/* fbar_set_lists(): Show statistics for a full header set */
void fbar_set_lists(fbar *b, ft_list *lists);
#endif /* _FT_WIDGETS_FBAR_H_ */

59
include/ft/widgets/fbrowser.h

@ -0,0 +1,59 @@
//---
// ft.widgets.browser: A dual-list setup to browse categorized tests
//---
#ifndef _FT_WIDGETS_BROWSER_H_
#define _FT_WIDGETS_BROWSER_H_
#include <justui/jwidget.h>
#include <ft/widgets/flist.h>
#include <ft/widgets/fbar.h>
#include <ft/test.h>
/* fbrowser: A test browser that shows categorized tests and summaries
This widget is the main frame of this application. It consists of list of
headers on the left, and a list of tests on the right. Summaries of all
headers and of all tests within a single header are shown. */
typedef struct {
jwidget widget;
/* Header list and test list */
flist *headers;
flist *tests;
/* Main bar at the top, and secondary bar above test list */
fbar *bar_top;
fbar *bar_right;
/* Widgets that help with the structure */
jwidget *rstack;
/* Current set of headers and tests */
ft_list *data_headers;
ft_test **data_tests;
} fbrowser;
/* A test entry has been validated */
extern uint16_t FBROWSER_VALIDATED;
/* fbrowser_create(): Create a test browser */
fbrowser *fbrowser_create(void *parent);
/* fbrowser_set_headers(): Select a set of headers
If (select = true), this also selects the first header.*/
void fbrowser_set_headers(fbrowser *b, ft_list *list, bool select);
/* fbrowser_set_tests(): Select a set of tests
You should not call this function; fbrowser synchronizes the set of tests
with the currently selected header. */
void fbrowser_set_tests(fbrowser *b, ft_test **tests);
/* fbrowser_current_header(): Currently-selected header
This function returns a pointer to the currently-selected list, which is
NULL if the summary is selected on the left panel, and non-NULL otherwise.
This is defined even when the focus is on the right panel. */
ft_list *fbrowser_current_header(fbrowser *b);
/* fbrowser_current_test(): Currently-selected test
This function returns a pointer to the currently-selected test, which is
non-NULL only when the focus is on the right panel. */
ft_test *fbrowser_current_test(fbrowser *b);
#endif /* _FT_WIDGETS_BROWSER_H_ */

79
include/ft/widgets/flist.h

@ -0,0 +1,79 @@
//---
// ft.widgets.flist: Scrolling list with selectable elements
//---
#ifndef _FT_WIDGETS_FLIST_H_
#define _FT_WIDGETS_FLIST_H_
#include <justui/jwidget.h>
#include <gint/display.h>
#include <gint/defs/call.h>
/* flist: A scrolling list with a selection cursor
This widget is a list of horizontal entries of a fixed height. The rendering
of entries and data storage is delegated to caller code. This widget only
handles the geometry and has no idea about the contents of the entries. */
typedef struct flist {
jwidget widget;
/* Renderer function */
gint_call_t renderer;
/* Number of rows, number of rows visible on-screen, current top row, and
currently-selected row (-1 if none) */
uint16_t rows;
uint16_t visible;
uint16_t top;
int16_t selected;
/* Row height */
uint16_t row_height;
/* Parameters available during rendering */
int16_t x, y;
int16_t row;
} flist;
/* The cursor has moved to a different entry */
extern uint16_t FLIST_SELECTED;
/* The current entry has been validated with EXE */
extern uint16_t FLIST_VALIDATED;
/* flist_create(): Create a scrolling list
Rows are dynamic, this function only sets the rendering function, which
should have the following prototype:
void render(flist *f, ...);
The GINT_CALL() for the renderer can use argument 2 to 4, the first will be
overridden by the flist code during the call.
The renderer can access the dimensions and row_height setting of the flist
widget, as well as draw_x, draw_y, draw_row and selected. Note however that
a couple of pixels may be reserved on the right to render a scrollbar. */
flist *flist_create(gint_call_t renderer, void *parent);
/* flist_set_renderer(): Change renderer after creation */
void flist_set_renderer(flist *l, gint_call_t renderer);
//---
// Configuration of rows
//---
/* flist_set_rows(): Set the number of rows */
void flist_set_rows(flist *l, int rows);
/* flist_set_row_height(): Fix the row height for every row */
void flist_set_row_height(flist *l, int row_height);
//---
// Movement
//---
/* flist_scroll_to(): Scroll to the specified offset */
void flist_scroll_to(flist *l, int offset);
/* flist_select(): Select an entry and scroll to it if needed */
void flist_select(flist *l, int entry);
/* flist_end(): Offset of the end of the list */
int flist_end(flist *l);
#endif /* _FT_WIDGETS_FLIST_H_ */

110
include/ft/widgets/gscreen.h

@ -0,0 +1,110 @@
//---
// gintctl.widgets.gscreen: gintctl's extended standard scene
//---
#ifndef _GINTCTL_WIDGETS_GSCREEN
#define _GINTCTL_WIDGETS_GSCREEN
#include <justui/jscene.h>
#include <justui/jlabel.h>
#include <justui/jfkeys.h>
#include <ft/util.h>
#include <gint/display.h>
struct gscreen_tab;
/* gscreen: gintctl's standard screen.
The gscreen is a scene with a stylized title bar, an optional function key
bar at the bottom, and a stacked layout of tabs in the middle. Each tab can
have specific properties set, and the gscreen handles some of the focus
management caused by hiding and showing a lot of widgets in tabs.
The scene of a gscreen can be accessed with (the_gscreen->scene). */
typedef struct {
jscene *scene;
/* Number of tabs */
int tab_count;
/* Information on tabs */
struct gscreen_tab *tabs;
/* Fixed widets */
jlabel *title;
jfkeys *fkeys;
/* Current function bar level */
int8_t fkey_level;
} gscreen;
struct gscreen_tab {
bool title_visible, fkeys_visible;
/* Most recent focused widget one */
jwidget *focus;
};
/* gscreen_create(): Set up a standard scene.
If (title = NULL), the scene has no title bar, if (fkeys = NULL) it has no
function bar. To show a title/function bar on some tabs but not all, create
one here and use gscreen_set_tab_{title,fkeys}_visible(). */
#ifdef FX9860G
gscreen *gscreen_create(char const *title, bopti_image_t const *fkeys);
#define gscreen_create2(short, img, long, fkeys) gscreen_create(short, img)
#endif
#ifdef FXCG50
gscreen *gscreen_create(char const *title, char const *fkeys);
#define gscreen_create2(short, img, long, fkeys) gscreen_create(long, fkeys)
#endif
void gscreen_destroy(gscreen *s);
//---
// Function bar settings
//---
/* gscreen_set_fkeys_level(): Select the function key bar */
void gscreen_set_fkeys_level(gscreen *s, int level);
//---
// Tab settings
//---
/* gscreen_add_tab(): Add a tab to the stacked layout
The child's parent will be changed. The widget will also have a stretch of
(1, 1, false) set by default. If not NULL, the last parameter indicates
which widget to focus when the tab is first shown. */
void gscreen_add_tab(gscreen *scene, void *widget, void *focus);
/* gcreen_add_tabs(): Add several tabs at once, with no special parameters */
void gscreen_add_tabs(gscreen *s, ...);
#define gscreen_add_tabs(...) gscreen_add_tabs(__VA_ARGS__, NULL)
/* gscreen_set_tab_title_visible(): Set whether title bar is shown on a tab */
void gscreen_set_tab_title_visible(gscreen *s, int tab, bool visible);
/* gscreen_set_tab_fkeys_visible(): Set whether fkeys are shown on a tab */
void gscreen_set_tab_fkeys_visible(gscreen *s, int tab, bool visible);
//---
// Tab navigation
//---
/* gscreen_show_tab(): Show a tab from the stack
Returns true if the tab changed, false if it was already active or if it is
an invalid tab widget. */
bool gscreen_show_tab(gscreen *s, int tab);
/* gscreen_current_tab(): Give the currently visible tab */
int gscreen_current_tab(gscreen *s);
/* gscreen_in(): Check if we're in a specific tab */
bool gscreen_in(gscreen *s, int tab);
//---
// Focus management
//---
/* Set focus for the current tab */
void gscreen_focus(gscreen *s, void *widget);
#endif /* _GINTCTL_WIDGETS_GSCREEN */

28
src/ctype/trivialties.c

@ -0,0 +1,28 @@
#include <ft/test.h>
#include <ft/all-tests.h>
static void _ctype_trivial_success(ft_test *t)
{
ft_assert(t, 1);
ft_assert(t, 1);
}
ft_test ft_ctype_trivial_success = {
.name = "Trivialty (always succeeds)",
.function = _ctype_trivial_success,
};
static void _ctype_trivial_failure(ft_test *t)
{
ft_assert(t, 0);
}
ft_test ft_ctype_trivial_failure = {
.name = "Trivialty (always fails)",
.function = _ctype_trivial_failure,
};
ft_test ft_ctype_trivial_empty = {
.name = "Trivialty (no function)",
.function = NULL,
};

142
src/main.c

@ -0,0 +1,142 @@
#include <gint/display.h>
#include <gint/keyboard.h>
#include <ft/widgets/gscreen.h>
#include <ft/widgets/flist.h>
#include <ft/widgets/fbrowser.h>
#include <ft/test.h>
#include <ft/all-tests.h>
/* We don't initialize the test result fields below */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
ft_list headers_libc[] = {
{ "<ctype.h>", (ft_test*[]){
&ft_ctype_trivial_success,
&ft_ctype_trivial_failure,
&ft_ctype_trivial_empty,
NULL,
}},
{ "<locale.h>", NULL },
{ "<setjmp.h>", NULL },
{ "<signal.h>", NULL },
{ "<stdio.h>", NULL },/*(ft_test[]){
{ "Formatted printing basics", NULL, NULL },
{ "Format types and options", NULL, NULL },
{ NULL },
}}, */
{ "<stdlib.h>", NULL },
{ "<string.h>", (ft_test*[]){
&ft_string_memset,
&ft_string_memcpy,
&ft_string_memmove,
NULL,
}},
{ "<time.h>", NULL },
{ "<uchar.h>", NULL },
{ "<wchar.h>", NULL },
{ "<wctype.h>", NULL },
{ NULL }
};
ft_list headers_posix[] = {
{ "<unistd.h>", NULL },
{ NULL }
};
#pragma GCC diagnostic pop
int main(void)
{
// Initialize test results
for(int i = 0; headers_libc[i].name; i++)
ft_list_init(&headers_libc[i]);
for(int i = 0; headers_posix[i].name; i++)
ft_list_init(&headers_posix[i]);
// Create GUI
gscreen *scr = gscreen_create2("FxLibc tests", NULL,
"FxLibc regression and performance tests", "/LIBC;/POSIX;;;;#RUN");
fbrowser *browser = fbrowser_create(NULL);
int selected_set = 0;
gscreen_add_tab(scr, browser, NULL);
fbrowser_set_headers(browser, headers_libc, false);
jwidget *results = jwidget_create(NULL);
gscreen_add_tab(scr, results, NULL);
gscreen_set_tab_fkeys_visible(scr, 1, false);
// Event handling
while(1) {
jevent e = jscene_run(scr->scene);
int tab = gscreen_current_tab(scr);
int key = 0;
if(e.type == JSCENE_KEY || e.key.type == KEYEV_DOWN) key = e.key.key;
if(e.type == JSCENE_PAINT) {
dclear(C_WHITE);
jscene_render(scr->scene);
dupdate();
}
// Switch lists on F1/F2
if(tab == 0 && key == KEY_F1 && selected_set != 0) {
fbrowser_set_headers(browser, headers_libc, false);
selected_set = 0;
}
if(tab == 0 && key == KEY_F2 && selected_set != 1) {
fbrowser_set_headers(browser, headers_posix, false);
selected_set = 1;
}
// Run tests
if(tab == 0 && key == KEY_F6) {
ft_list *header = fbrowser_current_header(browser);
ft_test *test = fbrowser_current_test(browser);
if(test) {
ft_test_run(test);
ft_list_aggregate(header);
browser->widget.update = 1;
}
else if(header) {
ft_list_run(header);
browser->widget.update = 1;
}
else {
for(int i = 0; headers_libc[i].name; i++)
ft_list_run(&headers_libc[i]);
for(int i = 0; headers_posix[i].name; i++)
ft_list_run(&headers_posix[i]);
browser->widget.update = 1;
}
}
// Browse test results
if(tab == 0 && e.type == FBROWSER_VALIDATED && e.source == browser) {
ft_test *test = fbrowser_current_test(browser);
if(ft_test_done(test) && test->widget) {
jwidget *w = test->widget(test);
if(w) {
jwidget_add_child(results, w);
gscreen_show_tab(scr, 1);
jscene_set_focused_widget(scr->scene, w);
}
}
}
// Close test results
if(tab == 1 && key == KEY_EXIT) {
jscene_set_focused_widget(scr->scene, browser->tests);
gscreen_show_tab(scr, 0);
while(results->child_count > 0) {
jwidget_destroy(results->children[0]);
}
}
}
gscreen_destroy(scr);
return 1;
}

168
src/string/core.c

@ -0,0 +1,168 @@
#include <ft/test.h>
#include <ft/all-tests.h>
#include <gint/std/string.h>
#include "memarray.h"
//---
// Utilities
//---
/* Fill buffer with non-zero and position-sensitive data */
static void fill(uint8_t *buf, int size, int start)
{
for(int i = 0; i < size; i++) buf[i] = start+i;
}
/* Clear buffer */
static void clear(uint8_t *buf, int size)
{
for(int i = 0; i < size; i++) buf[i] = 0;
}
/* Check buffer equality (returns zero on equal) */
static int cmp(uint8_t *left, uint8_t *right, int size)
{
for(int i = 0; i < size; i++) if(left[i] != right[i]) return 1;
return 0;
}
//---
// Naive functions (baseline)
//---
static void *naive_memcpy(void *_dst, void const *_src, size_t len)
{
uint8_t *dst = _dst;
uint8_t const *src = _src;
while(len--) *dst++ = *src++;
return _dst;
}
static void *naive_memset(void *_dst, int byte, size_t len)
{
uint8_t *dst = _dst;
while(len--) *dst++ = byte;
return _dst;
}
static void *naive_memmove(void *dst, void const *src, size_t len)
{
uint8_t tmp[len];
naive_memcpy(tmp, src, len);
naive_memcpy(dst, tmp, len);
return dst;
}
static int naive_memcmp(void const *_s1, void const *_s2, size_t len)
{
uint8_t const *s1 = _s1, *s2 = _s2;
for(size_t i = 0; i < len; i++)
{
if(s1[i] != s2[i]) return s1[i] - s2[i];
}
return 0;
}
//---
// memset
//---
static int _string_memset_func(memarray_args_t const *args)
{
fill(args->full_buf1, args->full_size, 0);
fill(args->full_buf2, args->full_size, 0);
memset(args->buf1, 0, args->size);
naive_memset(args->buf2, 0, args->size);
return cmp(args->full_buf1, args->full_buf2, args->full_size);
}
uint8_t _string_memset_rc[MEMARRAY_RC_SINGLE];
static void _string_memset(ft_test *t)
{
memarray_single(_string_memset_rc, _string_memset_func);
memarray_assert(_string_memset_rc, t);
}
static jwidget *_string_memset_widget(GUNUSED ft_test *t)
{
return memarray_widget(_string_memset_rc);
}
ft_test ft_string_memset = {
.name = "Configurations of memset",
.function = _string_memset,
.widget = _string_memset_widget,
};
//---
// memcpy
//---
static int _string_memcpy_func(memarray_args_t const *args)
{
fill(args->full_left1, args->full_size, 0);
fill(args->full_left2, args->full_size, 0);
clear(args->full_right1, args->full_size);
clear(args->full_right2, args->full_size);
memcpy(args->right1, args->left1, args->size);
naive_memcpy(args->right2, args->left2, args->size);
return cmp(args->full_right1, args->full_right2, args->full_size);
}
uint8_t _string_memcpy_rc[MEMARRAY_RC_DOUBLE];
static void _string_memcpy(ft_test *t)
{
memarray_double(_string_memcpy_rc, false, _string_memcpy_func);
memarray_assert(_string_memcpy_rc, t);
}
static jwidget *_string_memcpy_widget(GUNUSED ft_test *t)
{
return memarray_widget(_string_memcpy_rc);
}
ft_test ft_string_memcpy = {
.name = "Configurations of memcpy",
.function = _string_memcpy,
.widget = _string_memcpy_widget,
};
//---
// memmove
//---
static int _string_memmove_func(memarray_args_t const *args)
{
fill(args->full_left1, args->full_size, 0);
fill(args->full_left2, args->full_size, 0);
fill(args->full_right1, args->full_size, 0);
fill(args->full_right2, args->full_size, 0);
memmove(args->right1, args->left1, args->size);
naive_memmove(args->right2, args->left2, args->size);
return cmp(args->full_right1, args->full_right2, args->full_size);
}
uint8_t _string_memmove_rc[MEMARRAY_RC_DOUBLE_OVERLAP];
static void _string_memmove(ft_test *t)
{
memarray_double(_string_memmove_rc, true, _string_memmove_func);
memarray_assert(_string_memmove_rc, t);
}
static jwidget *_string_memmove_widget(GUNUSED ft_test *t)
{
return memarray_widget(_string_memmove_rc);
}
ft_test ft_string_memmove = {
.name = "Configurations of memmove",
.function = _string_memmove,
.widget = _string_memmove_widget,
};

199
src/string/memarray.c

@ -0,0 +1,199 @@
#include <gint/defs/types.h>
#include <gint/std/string.h>
#include <gint/display.h>
#include <gint/keyboard.h>
#include <gint/exc.h>
#include <gint/defs/call.h>
#include <justui/jpainted.h>
#include <ft/util.h>
#include "memarray.h"
//---
// Utilities
//---
/* Code of exception that occurs during a memory access */
static uint32_t exception = 0;
/* Exception-catching function */
static int catch_exc(uint32_t code)
{
if(code == 0x100 || code == 0x0e0)
{
exception = code;
gint_exc_skip(1);
return 0;
}
return 1;
}
/* memory_protected_call(): Wrapper that protects against access errors */
static int memory_protected_call(gint_call_t function)
{
exception = 0;
gint_exc_catch(catch_exc);
int rc = gint_call(function);
gint_exc_catch(NULL);
return rc || (exception != 0);
}
//---
// Public API
//---
void memarray_set(uint8_t *rc, int position, int value)
{
int index = 1 + (position >> 3);
uint8_t byte = 0x80 >> (position & 7);
if(index >= rc[0]) return;
if(value) rc[index] |= byte;
else rc[index] &= ~byte;
}
int memarray_get(uint8_t *rc, int position)
{
int index = 1 + (position >> 3);
uint8_t byte = 0x80 >> (position & 7);
if(index >= rc[0]) return -1;
return (rc[index] & byte) != 0;
}
void memarray_assert(uint8_t *rc, ft_test *test)
{
for(int i = 0; true; i++) {
int v = memarray_get(rc, i);
if(v < 0) return;
ft_assert(test, v == 0);
}
}
void memarray_single(uint8_t *rc, memarray_func_t f)
{
uint8_t buf1[128], buf2[128];
int counter = 0;
rc[0] = MEMARRAY_RC_SINGLE;
memarray_args_t args = {
.full_buf1 = buf1,
.full_buf2 = buf2,
.full_size = 128,
};
gint_call_t call = GINT_CALL(f, (void *)&args);
for(int buf_al = 0; buf_al < 4; buf_al++)
for(int len_al = 0; len_al < 4; len_al++)
{
args.buf1 = buf1 + 8 + buf_al;
args.buf2 = buf2 + 8 + buf_al;
args.size = 12 + len_al;
memarray_set(rc, counter++, memory_protected_call(call));
args.buf1 = buf1 + 8 + buf_al;
args.buf2 = buf2 + 8 + buf_al;
args.size = 92 + len_al;
memarray_set(rc, counter++, memory_protected_call(call));
}
}
void memarray_double(uint8_t *rc, bool overlap, memarray_func_t f)
{
uint8_t left1[128], left2[128], right1[128], right2[128];
int counter = 0;
rc[0] = overlap ? MEMARRAY_RC_DOUBLE_OVERLAP : MEMARRAY_RC_DOUBLE;
memarray_args_t args = {
.full_left1 = left1,
.full_left2 = left2,
.full_right1 = right1,
.full_right2 = right2,
.full_size = 128,
};
gint_call_t call = GINT_CALL(f, (void *)&args);
for(int left_al = 0; left_al < 4; left_al++)
for(int right_al = 0; right_al < 4; right_al++)
for(int len_al = 0; len_al < 4; len_al++)
{
args.left1 = left1 + 8 + left_al;
args.left2 = left2 + 8 + left_al;
args.right1 = right1 + 8 + right_al;
args.right2 = right2 + 8 + right_al;
args.size = 12 + len_al;
memarray_set(rc, counter++, memory_protected_call(call));
args.left1 = left1 + 8 + left_al;
args.left2 = left2 + 8 + left_al;
args.right1 = right1 + 8 + right_al;
args.right2 = right2 + 8 + right_al;
args.size = 92 + len_al;
memarray_set(rc, counter++, memory_protected_call(call));
if(!overlap) continue;
/* In overlap mode, only use left1 and left2 to create an overlap */
args.left1 = left1 + left_al;
args.left2 = left2 + left_al;
args.right1 = left1 + 28 + right_al;
args.right2 = left2 + 28 + right_al;
args.size = 92 + len_al;
memarray_set(rc, counter++, memory_protected_call(call));
args.left1 = left1 + 28 + left_al;
args.left2 = left2 + 28 + left_al;
args.right1 = left1 + right_al;
args.right2 = left2 + right_al;
args.size = 92 + len_al;
memarray_set(rc, counter++, memory_protected_call(call));
}
}
//---
// Visualization widget
//---
static void paint_results(int x, int y, uint8_t *rc)
{
dprint_opt(x+222, y, C_BLACK, C_NONE, DTEXT_CENTER, DTEXT_TOP,
"Source align and destination align");
for(int i = 0; i < 16; i++)
dprint(x + 74 + 18*i + 3*(i/4), y+12, C_BLACK, "%d%d", i/4, i & 3);
int sizes = 0;
if(rc[0] == MEMARRAY_RC_SINGLE) sizes = 2;
if(rc[0] == MEMARRAY_RC_DOUBLE) sizes = 2;
if(rc[0] == MEMARRAY_RC_DOUBLE_OVERLAP) sizes = 4;
char const *size_names[4] = { "Small", "Large", "Over-L", "Over-R" };
for(int i = 0; i < 4; i++) {
dprint_opt(x+55, y + 43 + 42*i, C_BLACK, C_NONE, DTEXT_RIGHT,
DTEXT_MIDDLE, "%s", size_names[i]);
}
for(int i = 0; i < 16; i++)
dprint_opt(x+64, y + 25 + 10*i + 2*(i/4), C_BLACK, C_NONE,
DTEXT_CENTER, DTEXT_TOP, "%d", i & 3);
for(int row = 0; row < 16; row++)
for(int col = 0; col < 16; col++)
{
int x1 = x + 72 + 18*col + 3*(col/4);
int y1 = y + 24 + 10*row + 2*(row/4);
int value = -1;
if(row < 4*sizes) value = memarray_get(rc, 4*sizes * col + row);
int fg = (value == -1) ? C_WHITE : (value == 0) ? C_GREEN : C_RED;
drect_border(x1, y1, x1+19, y1+10, fg, 1, C_BLACK);
}
}
jwidget *memarray_widget(uint8_t *rc)
{
jpainted *p = jpainted_create(paint_results, rc, 377, 185, NULL);
if(!p) return NULL;
jwidget_set_margin(p, 4, 4, 4, 4);
return &p->widget;
}

96
src/string/memarray.h

@ -0,0 +1,96 @@
//---
// string.memarray: Alignment/size combination array for core memory functions
//
// This header provides tools to check memory functions in all size/alignment
// configurations. Up to two buffers can be involved (source and destination),
// and for each buffer all 4n + {0,1,2,3} alignments are tested. Different
// sizes are also tested, each with all possible 4n + {0,1,2,3} values.
//
// By default, two size configurations are tested:
// * 12..15 bytes (intended to use naive, minimum-logic methods)
// * 92..95 bytes (intended to use elaborate methods)
// If two buffers are involved, overlapping sections can also be requested:
// * (32-[64)-32] (approximately; alignment slightly alters values)
// * [32-(64]-32) (approximately; alignment slightly alters values)
//
// This header defines two functions memarray_single() and memarray_double()
// that repeatedly invoke a user-supplied test function with different
// combinations of alignments and sizes. The test function is guarded against
// misaligned memory accesses (which are detected and counted as failures) to
// avoid application crashes.
//
// When invoked, the test function is supplied with a memarray_args_t structure
// that gives buffer addresses and sizes for a single combination. Each buffer
// is provided twice ("name1" and "name2") so that tests requiring a baseline
// (eg. comparing optimized memcpy with naive memcpy) can run the baseline on
// the duplicate buffers.
//
// Test results are recorded in bitmaps based on the return value of the test
// function (0 for success, 1 for failure); this header provides memarray_set()
// and memarray_get() to use the bitmaps (although rarely needed), as well as
// memarray_assert() to record results into an ft_test and memarray_widget() to
// generate result visualizations.
//---
#ifndef _STRING_MEMARRAY_H
#define _STRING_MEMARRAY_H
#include <gint/defs/types.h>
#include <ft/test.h>
#include <justui/jwidget.h>
/* Size of result buffers to allocate for each configuration; one byte is
reserved to indicate the configuration for the bitmap functions */
/* Single buffer: 4 alignments * 8 sizes = 32 tests -> 4 bytes */
#define MEMARRAY_RC_SINGLE 5
/* Double buffer : 4 * 4 alignments * 8 sizes = 128 tests -> 16 bytes */
#define MEMARRAY_RC_DOUBLE 17
/* Double with overlap : 4 * 4 alignments * 16 sizes = 256 tests -> 32 bytes */
#define MEMARRAY_RC_DOUBLE_OVERLAP 33
/* Arguments to test functions:
* In single mode, buf1 and buf2 are used
* In double mode, left1, left2, right1 and right2 are used */
typedef struct {
/* Pointers with varying alignment, to test with */
union { void *buf1, *left1; };
union { void *buf2, *left2; };
void *right1;
void *right2;
/* Size of the operation to perform */
size_t size;
/* Pointers to the orignal buffers (which occupy a larger interval of
memory than the pointers above). This is useful to initialize and check
an area slightly larger than the tested area, to make sure that tested
functions don't write outside their assigned bounds */
union { void *full_buf1, *full_left1; };
union { void *full_buf2, *full_left2; };
void *full_right1;
void *full_right2;
/* Size of the original buffers (128) */
size_t full_size;
} memarray_args_t;
/* Type of test functions; should return 0 for success, 1 for failure */
typedef int (*memarray_func_t)(memarray_args_t const *args);
/* Run a test; the result buffer must have a suitable size allocated */
void memarray_single(uint8_t *rc, memarray_func_t f);
void memarray_double(uint8_t *rc, bool do_overlaps, memarray_func_t f);
/* Set a bit in the result buffer; if position is out of bounds, no-op */
void memarray_set(uint8_t *rc, int position, int value);
/* Get a bit in the result buffer; if position is out of bounds, returns -1 */
int memarray_get(uint8_t *rc, int position);
/* Assert that every value in the result array is 0 */
void memarray_assert(uint8_t *rc, ft_test *test);
/* Create a widget to visualize the contents of a result buffer. The result
pointer is not tied to the widget so it will continue to exist even after
the widget is destroyed. */
jwidget *memarray_widget(uint8_t *buffer);
#endif /* _STRING_MEMARRAY_H */

114
src/test.c

@ -0,0 +1,114 @@
#include <ft/test.h>
#include <stdarg.h>
#undef ft_assert
//---
// Test framework
//---
void ft_test_init(ft_test *test)
{
test->passed = 0;
test->skipped = 0;
test->failed = 0;
}
void ft_test_run(ft_test *test)
{
ft_test_init(test);
if(!test->function) return;
test->function(test);
}
bool ft_test_done(ft_test *test)
{
return test->passed + test->skipped + test->failed > 0;
}
void ft_assert(ft_test *test, int expression, char const *file, int line,
char const *str)
{
if(expression) {
test->passed++;
}
else {
test->failed++;
ft_log(test, "%s:%d: assertion '%s' failed", file, line, str);
}
}
void ft_log(ft_test *test, char const *format, ...)
{
}
//---
// Test lists (to organize by header)
//---
void ft_list_init(ft_list *list)
{
list->passed = 0;
list->skipped = 0;
list->failed = 0;
if(!list->children) return;
for(int i = 0; list->children[i]; i++) {
ft_test_init(list->children[i]);
}
}
void ft_list_run(ft_list *list)
{
ft_list_init(list);
if(!list->children) return;
for(int i = 0; list->children[i]; i++) {
ft_test_run(list->children[i]);
}
ft_list_aggregate(list);
}
void ft_list_aggregate(ft_list *l)
{
l->passed = 0;
l->skipped = 0;
l->failed = 0;
if(!l->children) return;
for(int i = 0; l->children[i]; i++) {
ft_test const *t = l->children[i];
l->passed += t->passed;
l->skipped += t->skipped;
l->failed += t->failed;
}
}
//---
// Utilities
//---
int ft_test_color(ft_test const *t)
{
if(t->failed > 0) return FT_TEST_FAILED;
if(t->skipped > 0) return FT_TEST_SKIPPED;
if(t->passed > 0) return FT_TEST_PASSED;
if(t->function) return FT_TEST_PENDING;
return FT_TEST_EMPTY;
}
int ft_list_color(ft_list const *l)
{
if(l->failed > 0) return FT_TEST_FAILED;
if(l->skipped > 0) return FT_TEST_SKIPPED;
if(l->children) {
for(int i = 0; l->children[i]; i++) {
if(l->children[i]->function && l->children[i]->passed == 0)
return FT_TEST_PENDING;
}
}
if(l->passed > 0) return FT_TEST_PASSED;
return FT_TEST_EMPTY;
}

187
src/widgets/fbar.c

@ -0,0 +1,187 @@
#include <ft/widgets/fbar.h>
#include <justui/jwidget-api.h>
#include <gint/std/stdlib.h>
#include <gint/std/string.h>
/* Type identifier for fbar */
static int fbar_type_id = -1;
fbar *fbar_create(void *parent)
{
if(fbar_type_id < 0) return NULL;
fbar *b = malloc(sizeof *b);
if(!b) return NULL;
jwidget_init(&b->widget, fbar_type_id, parent);
b->tests = NULL;
b->lists = NULL;
jwidget_set_margin(b, 0, 8, 2, 8);
return b;
}
void fbar_set_tests(fbar *b, ft_test **tests)
{
b->tests = tests;
b->lists = NULL;
b->widget.update = 1;
}
void fbar_set_lists(fbar *b, ft_list *lists)
{
b->tests = NULL;
b->lists = lists;
b->widget.update = 1;
}
//---
// Counting statistics
//---
struct stats {
/* Number of tests done, pending and empty (proportion of full bar) */
int16_t done;
int16_t pending;
int16_t empty;
/* Number of assertions (proportion of the done section of the bar) */
int16_t passed;
int16_t skipped;
int16_t failed;
};
static void stats_init(struct stats *st)
{
memset(st, 0, sizeof *st);
}
static void stats_add_test(struct stats *st, ft_test *t)
{
st->passed += t->passed;
st->skipped += t->skipped;
st->failed += t->failed;
if(!t->function) st->empty++;
else if(t->passed + t->skipped + t->failed == 0) st->pending++;
else st->done++;
}
static void stats_add_list(struct stats *st, ft_list *l)
{
if(!l->children) return;
for(int i = 0; l->children[i]; i++)
stats_add_test(st, l->children[i]);
}
//---
// Polymorphic widget operations
//---
static void fbar_poly_csize(void *b0)
{
fbar *b = b0;
b->widget.w = 120;
b->widget.h = 13;
}
static void fbar_poly_render(void *b0, int x, int y)
{
fbar *b = b0;
int w = jwidget_content_width(b);
int h = jwidget_content_height(b);
struct stats st, px;
stats_init(&st);
stats_init(&px);
if(b->tests) for(int i = 0; b->tests[i]; i++) {
stats_add_test(&st, b->tests[i]);
}
else if(b->lists) for(int i = 0; b->lists[i].name; i++) {
stats_add_list(&st, &b->lists[i]);
}
/* Width of bar for each of the 3 main sections */
int main = st.done + st.pending + st.empty;
if(main == 0) {
drect_border(x, y, x+w-1, y+h-1, C_WHITE, 1, C_BLACK);
return;
}
/* Try go guarantee at least 10 pixels per section */
int guaranteeable_min = 10 * (!!st.done + !!st.pending + !!st.empty);
bool guaranteed = (w > guaranteeable_min);
if(guaranteed) w -= guaranteeable_min;
px.done = (st.done * w) / main;
px.pending = (st.pending * w) / main;
px.empty = w - px.done - px.pending;
if(guaranteed) {
if(st.done) px.done += 10;
if(st.pending) px.pending += 10;
if(st.empty) px.empty += 10;
w += guaranteeable_min;
}
/* Width of bar for each of the subsections of done */
int assertions = st.passed + st.skipped + st.failed;
if(assertions == 0) assertions = 1;
guaranteeable_min = 10 * (!!st.passed + !!st.skipped + !!st.failed);
guaranteed = (px.done > guaranteeable_min);
if(guaranteed) px.done -= guaranteeable_min;
px.passed = (st.passed * px.done) / assertions;
px.skipped = (st.skipped * px.done) / assertions;
px.failed = px.done - px.passed - px.skipped;
if(guaranteed) {
if(st.passed) px.passed += 10;
if(st.skipped) px.skipped += 10;
if(st.failed) px.failed += 10;
px.done += guaranteeable_min;
}