From 34487537ceb62ee2e2fabc0667e65c43a1319855 Mon Sep 17 00:00:00 2001 From: Sebastian Huber Date: Tue, 4 Jul 2017 09:57:30 +0200 Subject: score: Add simple affinity support to EDF SMP Update #3059. --- testsuites/smptests/Makefile.am | 2 + testsuites/smptests/configure.ac | 2 + testsuites/smptests/smpschededf01/init.c | 4 +- testsuites/smptests/smpschededf02/Makefile.am | 19 + testsuites/smptests/smpschededf02/init.c | 383 +++++++++++++++++++++ .../smptests/smpschededf02/smpschededf02.doc | 11 + .../smptests/smpschededf02/smpschededf02.scn | 0 testsuites/smptests/smpschededf03/Makefile.am | 19 + testsuites/smptests/smpschededf03/init.c | 160 +++++++++ .../smptests/smpschededf03/smpschededf03.doc | 12 + .../smptests/smpschededf03/smpschededf03.scn | 2 + testsuites/smptests/smpscheduler07/init.c | 2 +- 12 files changed, 614 insertions(+), 2 deletions(-) create mode 100644 testsuites/smptests/smpschededf02/Makefile.am create mode 100644 testsuites/smptests/smpschededf02/init.c create mode 100644 testsuites/smptests/smpschededf02/smpschededf02.doc create mode 100644 testsuites/smptests/smpschededf02/smpschededf02.scn create mode 100644 testsuites/smptests/smpschededf03/Makefile.am create mode 100644 testsuites/smptests/smpschededf03/init.c create mode 100644 testsuites/smptests/smpschededf03/smpschededf03.doc create mode 100644 testsuites/smptests/smpschededf03/smpschededf03.scn (limited to 'testsuites/smptests') diff --git a/testsuites/smptests/Makefile.am b/testsuites/smptests/Makefile.am index 01dc52e524..40f0b0fd7a 100644 --- a/testsuites/smptests/Makefile.am +++ b/testsuites/smptests/Makefile.am @@ -36,6 +36,8 @@ _SUBDIRS += smpschedaffinity03 _SUBDIRS += smpschedaffinity04 _SUBDIRS += smpschedaffinity05 _SUBDIRS += smpschededf01 +_SUBDIRS += smpschededf02 +_SUBDIRS += smpschededf03 _SUBDIRS += smpschedsem01 _SUBDIRS += smpscheduler01 _SUBDIRS += smpscheduler02 diff --git a/testsuites/smptests/configure.ac b/testsuites/smptests/configure.ac index f3a840b593..d72f6e072d 100644 --- a/testsuites/smptests/configure.ac +++ b/testsuites/smptests/configure.ac @@ -91,6 +91,8 @@ smpschedaffinity03/Makefile smpschedaffinity04/Makefile smpschedaffinity05/Makefile smpschededf01/Makefile +smpschededf02/Makefile +smpschededf03/Makefile smpschedsem01/Makefile smpscheduler01/Makefile smpscheduler02/Makefile diff --git a/testsuites/smptests/smpschededf01/init.c b/testsuites/smptests/smpschededf01/init.c index c1c995e69b..6b250cf699 100644 --- a/testsuites/smptests/smpschededf01/init.c +++ b/testsuites/smptests/smpschededf01/init.c @@ -140,11 +140,13 @@ static void Init(rtems_task_argument arg) #define CONFIGURE_MAXIMUM_TASKS 3 #define CONFIGURE_MAXIMUM_PERIODS 2 +#define CONFIGURE_MAXIMUM_PROCESSORS 1 + #define CONFIGURE_SCHEDULER_EDF_SMP #include -RTEMS_SCHEDULER_CONTEXT_EDF_SMP(a); +RTEMS_SCHEDULER_CONTEXT_EDF_SMP(a, CONFIGURE_MAXIMUM_PROCESSORS); #define CONFIGURE_SCHEDULER_CONTROLS \ RTEMS_SCHEDULER_CONTROL_EDF_SMP(a, rtems_build_name('E', 'D', 'F', ' ')) diff --git a/testsuites/smptests/smpschededf02/Makefile.am b/testsuites/smptests/smpschededf02/Makefile.am new file mode 100644 index 0000000000..6e9e01cb60 --- /dev/null +++ b/testsuites/smptests/smpschededf02/Makefile.am @@ -0,0 +1,19 @@ +rtems_tests_PROGRAMS = smpschededf02 +smpschededf02_SOURCES = init.c + +dist_rtems_tests_DATA = smpschededf02.scn smpschededf02.doc + +include $(RTEMS_ROOT)/make/custom/@RTEMS_BSP@.cfg +include $(top_srcdir)/../automake/compile.am +include $(top_srcdir)/../automake/leaf.am + +AM_CPPFLAGS += -I$(top_srcdir)/../support/include + +LINK_OBJS = $(smpschededf02_OBJECTS) +LINK_LIBS = $(smpschededf02_LDLIBS) + +smpschededf02$(EXEEXT): $(smpschededf02_OBJECTS) $(smpschededf02_DEPENDENCIES) + @rm -f smpschededf02$(EXEEXT) + $(make-exe) + +include $(top_srcdir)/../automake/local.am diff --git a/testsuites/smptests/smpschededf02/init.c b/testsuites/smptests/smpschededf02/init.c new file mode 100644 index 0000000000..e0c5182f2e --- /dev/null +++ b/testsuites/smptests/smpschededf02/init.c @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2016, 2017 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Dornierstr. 4 + * 82178 Puchheim + * Germany + * + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.org/license/LICENSE. + */ + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#include "tmacros.h" + +#include + +const char rtems_test_name[] = "SMPSCHEDEDF 2"; + +#define CPU_COUNT 2 + +#define TASK_COUNT 5 + +#define P(i) (UINT32_C(2) + i) + +#define A(cpu0, cpu1) ((cpu1 << 1) | cpu0) + +#define IDLE UINT8_C(255) + +#define NAME rtems_build_name('E', 'D', 'F', ' ') + +typedef struct { + enum { + KIND_RESET, + KIND_SET_PRIORITY, + KIND_SET_AFFINITY, + KIND_BLOCK, + KIND_UNBLOCK + } kind; + + size_t index; + + struct { + rtems_task_priority priority; + uint32_t cpu_set; + } data; + + uint8_t expected_cpu_allocations[CPU_COUNT]; +} test_action; + +typedef struct { + rtems_id timer_id; + rtems_id master_id; + rtems_id task_ids[TASK_COUNT]; + size_t action_index; +} test_context; + +#define RESET \ + { \ + KIND_RESET, \ + 0, \ + { 0 }, \ + { IDLE, IDLE } \ + } + +#define SET_PRIORITY(index, prio, cpu0, cpu1) \ + { \ + KIND_SET_PRIORITY, \ + index, \ + { .priority = prio }, \ + { cpu0, cpu1 } \ + } + +#define SET_AFFINITY(index, aff, cpu0, cpu1) \ + { \ + KIND_SET_AFFINITY, \ + index, \ + { .cpu_set = aff }, \ + { cpu0, cpu1 } \ + } + +#define BLOCK(index, cpu0, cpu1) \ + { \ + KIND_BLOCK, \ + index, \ + { 0 }, \ + { cpu0, cpu1 } \ + } + +#define UNBLOCK(index, cpu0, cpu1) \ + { \ + KIND_UNBLOCK, \ + index, \ + { 0 }, \ + { cpu0, cpu1 } \ + } + +static const test_action test_actions[] = { + RESET, + UNBLOCK( 0, 0, IDLE), + UNBLOCK( 1, 0, 1), + UNBLOCK( 3, 0, 1), + SET_PRIORITY( 1, P(2), 0, 1), + SET_PRIORITY( 3, P(1), 0, 3), + BLOCK( 3, 0, 1), + SET_AFFINITY( 1, A(1, 1), 0, 1), + SET_AFFINITY( 1, A(1, 0), 1, 0), + SET_AFFINITY( 1, A(1, 1), 1, 0), + SET_AFFINITY( 1, A(1, 0), 1, 0), + SET_AFFINITY( 1, A(0, 1), 0, 1), + BLOCK( 0, IDLE, 1), + UNBLOCK( 0, 0, 1), + BLOCK( 1, 0, IDLE), + UNBLOCK( 1, 0, 1), + RESET, + /* + * Show that FIFO order is honoured across all threads of the same priority. + */ + SET_PRIORITY( 1, P(0), IDLE, IDLE), + SET_PRIORITY( 2, P(1), IDLE, IDLE), + SET_PRIORITY( 3, P(1), IDLE, IDLE), + SET_AFFINITY( 3, A(1, 0), IDLE, IDLE), + SET_PRIORITY( 4, P(1), IDLE, IDLE), + SET_AFFINITY( 4, A(1, 0), IDLE, IDLE), + UNBLOCK( 0, 0, IDLE), + UNBLOCK( 1, 0, 1), + UNBLOCK( 2, 0, 1), + UNBLOCK( 3, 0, 1), + UNBLOCK( 4, 0, 1), + BLOCK( 1, 0, 2), + BLOCK( 2, 3, 0), + BLOCK( 3, 4, 0), + RESET, + /* + * Schedule a high priority affine thread directly with a low priority affine + * thread in the corresponding ready queue. In this case we, remove the + * affine ready queue in _Scheduler_EDF_SMP_Allocate_processor(). + */ + UNBLOCK( 0, 0, IDLE), + UNBLOCK( 1, 0, 1), + SET_PRIORITY( 1, P(2), 0, 1), + SET_AFFINITY( 3, A(0, 1), 0, 1), + UNBLOCK( 3, 0, 1), + SET_PRIORITY( 2, P(1), 0, 1), + SET_AFFINITY( 2, A(0, 1), 0, 1), + UNBLOCK( 2, 0, 2), + BLOCK( 1, 0, 2), + BLOCK( 2, 0, 3), + RESET +}; + +static test_context test_instance; + +static void set_priority(rtems_id id, rtems_task_priority prio) +{ + rtems_status_code sc; + + sc = rtems_task_set_priority(id, prio, &prio); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); +} + +static void set_affinity(rtems_id id, uint32_t cpu_set_32) +{ + rtems_status_code sc; + cpu_set_t cpu_set; + size_t i; + + CPU_ZERO(&cpu_set); + + for (i = 0; i < CPU_COUNT; ++i) { + if ((cpu_set_32 & (UINT32_C(1) << i)) != 0) { + CPU_SET(i, &cpu_set); + } + } + + sc = rtems_task_set_affinity(id, sizeof(cpu_set), &cpu_set); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); +} + +static void reset(test_context *ctx) +{ + rtems_status_code sc; + size_t i; + + for (i = 0; i < TASK_COUNT; ++i) { + set_priority(ctx->task_ids[i], P(i)); + set_affinity(ctx->task_ids[i], A(1, 1)); + } + + for (i = CPU_COUNT; i < TASK_COUNT; ++i) { + sc = rtems_task_suspend(ctx->task_ids[i]); + rtems_test_assert(sc == RTEMS_SUCCESSFUL || sc == RTEMS_ALREADY_SUSPENDED); + } + + for (i = 0; i < CPU_COUNT; ++i) { + sc = rtems_task_resume(ctx->task_ids[i]); + rtems_test_assert(sc == RTEMS_SUCCESSFUL || sc == RTEMS_INCORRECT_STATE); + } + + /* Order the idle threads explicitly */ + for (i = 0; i < CPU_COUNT; ++i) { + const Per_CPU_Control *c; + const Thread_Control *h; + + c = _Per_CPU_Get_by_index(CPU_COUNT - 1 - i); + h = c->heir; + + sc = rtems_task_suspend(h->Object.id); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + } +} + +static void check_cpu_allocations(test_context *ctx, const test_action *action) +{ + size_t i; + + for (i = 0; i < CPU_COUNT; ++i) { + size_t e; + const Per_CPU_Control *c; + const Thread_Control *h; + + e = action->expected_cpu_allocations[i]; + c = _Per_CPU_Get_by_index(i); + h = c->heir; + + if (e != IDLE) { + rtems_test_assert(h->Object.id == ctx->task_ids[e]); + } else { + rtems_test_assert(h->is_idle); + } + } +} + +/* + * Use a timer to execute the actions, since it runs with thread dispatching + * disabled. This is necessary to check the expected processor allocations. + */ +static void timer(rtems_id id, void *arg) +{ + test_context *ctx; + rtems_status_code sc; + size_t i; + + ctx = arg; + i = ctx->action_index; + + if (i == 0) { + sc = rtems_task_suspend(ctx->master_id); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + } + + if (i < RTEMS_ARRAY_SIZE(test_actions)) { + const test_action *action = &test_actions[i]; + rtems_id task; + + ctx->action_index = i + 1; + + task = ctx->task_ids[action->index]; + + switch (action->kind) { + case KIND_SET_PRIORITY: + set_priority(task, action->data.priority); + break; + case KIND_SET_AFFINITY: + set_affinity(task, action->data.cpu_set); + break; + case KIND_BLOCK: + sc = rtems_task_suspend(task); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + break; + case KIND_UNBLOCK: + sc = rtems_task_resume(task); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + break; + default: + rtems_test_assert(action->kind == KIND_RESET); + reset(ctx); + break; + } + + check_cpu_allocations(ctx, action); + + sc = rtems_timer_reset(id); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + } else { + sc = rtems_task_resume(ctx->master_id); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + + sc = rtems_event_transient_send(ctx->master_id); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + } +} + +static void do_nothing_task(rtems_task_argument arg) +{ + (void) arg; + + while (true) { + /* Do nothing */ + } +} + +static void test(void) +{ + test_context *ctx; + rtems_status_code sc; + size_t i; + + ctx = &test_instance; + + ctx->master_id = rtems_task_self(); + + for (i = 0; i < TASK_COUNT; ++i) { + sc = rtems_task_create( + NAME, + P(i), + RTEMS_MINIMUM_STACK_SIZE, + RTEMS_DEFAULT_MODES, + RTEMS_DEFAULT_ATTRIBUTES, + &ctx->task_ids[i] + ); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + + sc = rtems_task_start(ctx->task_ids[i], do_nothing_task, 0); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + } + + sc = rtems_timer_create(NAME, &ctx->timer_id); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + + sc = rtems_timer_fire_after(ctx->timer_id, 1, timer, ctx); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + + sc = rtems_event_transient_receive(RTEMS_WAIT, RTEMS_NO_TIMEOUT); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + + for (i = 0; i < TASK_COUNT; ++i) { + sc = rtems_task_delete(ctx->task_ids[i]); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + } + + sc = rtems_timer_delete(ctx->timer_id); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); +} + +static void Init(rtems_task_argument arg) +{ + TEST_BEGIN(); + + if (rtems_get_processor_count() == CPU_COUNT) { + test(); + } else { + puts("warning: wrong processor count to run the test"); + } + + TEST_END(); + rtems_test_exit(0); +} + +#define CONFIGURE_MICROSECONDS_PER_TICK 1000 + +#define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER +#define CONFIGURE_APPLICATION_NEEDS_CONSOLE_DRIVER + +#define CONFIGURE_MAXIMUM_TASKS (1 + TASK_COUNT) +#define CONFIGURE_MAXIMUM_TIMERS 1 + +#define CONFIGURE_MAXIMUM_PROCESSORS CPU_COUNT + +#define CONFIGURE_SCHEDULER_EDF_SMP + +#define CONFIGURE_INITIAL_EXTENSIONS RTEMS_TEST_INITIAL_EXTENSION + +#define CONFIGURE_RTEMS_INIT_TASKS_TABLE + +#define CONFIGURE_INIT + +#include diff --git a/testsuites/smptests/smpschededf02/smpschededf02.doc b/testsuites/smptests/smpschededf02/smpschededf02.doc new file mode 100644 index 0000000000..ece0e1a20e --- /dev/null +++ b/testsuites/smptests/smpschededf02/smpschededf02.doc @@ -0,0 +1,11 @@ +This file describes the directives and concepts tested by this test set. + +test set name: smpschededf02 + +directives: + + TBD + +concepts: + + TBD diff --git a/testsuites/smptests/smpschededf02/smpschededf02.scn b/testsuites/smptests/smpschededf02/smpschededf02.scn new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testsuites/smptests/smpschededf03/Makefile.am b/testsuites/smptests/smpschededf03/Makefile.am new file mode 100644 index 0000000000..e3496f50aa --- /dev/null +++ b/testsuites/smptests/smpschededf03/Makefile.am @@ -0,0 +1,19 @@ +rtems_tests_PROGRAMS = smpschededf03 +smpschededf03_SOURCES = init.c + +dist_rtems_tests_DATA = smpschededf03.scn smpschededf03.doc + +include $(RTEMS_ROOT)/make/custom/@RTEMS_BSP@.cfg +include $(top_srcdir)/../automake/compile.am +include $(top_srcdir)/../automake/leaf.am + +AM_CPPFLAGS += -I$(top_srcdir)/../support/include + +LINK_OBJS = $(smpschededf03_OBJECTS) +LINK_LIBS = $(smpschededf03_LDLIBS) + +smpschededf03$(EXEEXT): $(smpschededf03_OBJECTS) $(smpschededf03_DEPENDENCIES) + @rm -f smpschededf03$(EXEEXT) + $(make-exe) + +include $(top_srcdir)/../automake/local.am diff --git a/testsuites/smptests/smpschededf03/init.c b/testsuites/smptests/smpschededf03/init.c new file mode 100644 index 0000000000..33029532c6 --- /dev/null +++ b/testsuites/smptests/smpschededf03/init.c @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2017 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Dornierstr. 4 + * 82178 Puchheim + * Germany + * + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.org/license/LICENSE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "tmacros.h" + +#include + +const char rtems_test_name[] = "SMPSCHEDEDF 3"; + +#define CPU_COUNT 32 + +#define TASK_COUNT (3 * CPU_COUNT) + +typedef struct { + rtems_id task_ids[TASK_COUNT]; +} test_context; + +static test_context test_instance; + +static void wait_task(rtems_task_argument arg) +{ + (void) arg; + + while (true) { + rtems_status_code sc; + + sc = rtems_task_wake_after(1); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + } +} + +static uint32_t simple_random(uint32_t v) +{ + v *= 1664525; + v += 1013904223; + return v; +} + +static void affinity_task(rtems_task_argument arg) +{ + uint32_t v; + uint32_t n; + + v = (uint32_t) arg; + n = rtems_get_processor_count(); + + while (true) { + rtems_status_code sc; + cpu_set_t set; + + CPU_ZERO(&set); + CPU_SET((v >> 13) % n, &set); + v = simple_random(v); + + sc = rtems_task_set_affinity(RTEMS_SELF, sizeof(set), &set); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + } +} + +static void create_and_start_task( + test_context *ctx, + rtems_task_entry entry, + size_t i, + size_t j +) +{ + rtems_status_code sc; + + j = j * CPU_COUNT + i; + + sc = rtems_task_create( + rtems_build_name('E', 'D', 'F', ' '), + i + 2, + RTEMS_MINIMUM_STACK_SIZE, + RTEMS_DEFAULT_MODES, + RTEMS_DEFAULT_ATTRIBUTES, + &ctx->task_ids[j] + ); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + + sc = rtems_task_start(ctx->task_ids[j], entry, j); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); +} + +static void delete_task( + test_context *ctx, + size_t i, + size_t j +) +{ + rtems_status_code sc; + + j = j * CPU_COUNT + i; + + sc = rtems_task_delete(ctx->task_ids[j]); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); +} + +static void test(test_context *ctx) +{ + rtems_status_code sc; + size_t i; + + for (i = 0; i < CPU_COUNT; ++i) { + create_and_start_task(ctx, wait_task, i, 0); + create_and_start_task(ctx, affinity_task, i, 1); + create_and_start_task(ctx, affinity_task, i, 2); + } + + sc = rtems_task_wake_after(10 * rtems_clock_get_ticks_per_second()); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + + for (i = 0; i < CPU_COUNT; ++i) { + delete_task(ctx, i, 0); + delete_task(ctx, i, 1); + delete_task(ctx, i, 2); + } +} + +static void Init(rtems_task_argument arg) +{ + TEST_BEGIN(); + test(&test_instance); + TEST_END(); + rtems_test_exit(0); +} + +#define CONFIGURE_MICROSECONDS_PER_TICK 1000 + +#define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER +#define CONFIGURE_APPLICATION_NEEDS_CONSOLE_DRIVER + +#define CONFIGURE_MAXIMUM_TASKS (1 + TASK_COUNT) + +#define CONFIGURE_MAXIMUM_PROCESSORS CPU_COUNT + +#define CONFIGURE_SCHEDULER_EDF_SMP + +#define CONFIGURE_INITIAL_EXTENSIONS RTEMS_TEST_INITIAL_EXTENSION + +#define CONFIGURE_RTEMS_INIT_TASKS_TABLE + +#define CONFIGURE_INIT + +#include diff --git a/testsuites/smptests/smpschededf03/smpschededf03.doc b/testsuites/smptests/smpschededf03/smpschededf03.doc new file mode 100644 index 0000000000..1d11c42b21 --- /dev/null +++ b/testsuites/smptests/smpschededf03/smpschededf03.doc @@ -0,0 +1,12 @@ +This file describes the directives and concepts tested by this test set. + +test set name: smpschededf03 + +directives: + + - EDF SMP scheduler operations. + +concepts: + + - Randomized test case to show some stability of simple thread processor + affinity support of the EDF SMP scheduler. diff --git a/testsuites/smptests/smpschededf03/smpschededf03.scn b/testsuites/smptests/smpschededf03/smpschededf03.scn new file mode 100644 index 0000000000..1435f03920 --- /dev/null +++ b/testsuites/smptests/smpschededf03/smpschededf03.scn @@ -0,0 +1,2 @@ +*** BEGIN OF TEST SMPSCHEDEDF 3 *** +*** END OF TEST SMPSCHEDEDF 3 *** diff --git a/testsuites/smptests/smpscheduler07/init.c b/testsuites/smptests/smpscheduler07/init.c index cbffe89012..bb065b3844 100644 --- a/testsuites/smptests/smpscheduler07/init.c +++ b/testsuites/smptests/smpscheduler07/init.c @@ -32,7 +32,7 @@ const char rtems_test_name[] = "SMPSCHEDULER 7"; #include -RTEMS_SCHEDULER_CONTEXT_EDF_SMP(a); +RTEMS_SCHEDULER_CONTEXT_EDF_SMP(a, CONFIGURE_MAXIMUM_PROCESSORS); #define CONFIGURE_SCHEDULER_CONTROLS \ RTEMS_SCHEDULER_CONTROL_EDF_SMP( a, rtems_build_name('T', 'E', 'S', 'T')) -- cgit v1.2.3