feat(job-array-one-based-index): bounded job-identity and element-selection parser in libxce (task 1.1)
parse_jobsel() parses c-a-ts-seq with an optional [k] or [a-b] under a fixed grammar and check order (syntax, field widths, array-identity alignment, element bound, reversed range), saturating digits so an oversized number cannot wrap; plus is_array_tag(), jobsel_element_id(), jobsel_strerror() and JOBID_FIELD_MAX_* constants. The selection-cases conformance table runs in the libxce suite. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
590860f73d
commit
6e6c599d27
6 changed files with 501 additions and 1 deletions
|
|
@ -218,6 +218,65 @@ extern int parse_jobid(uint64_t*, char*);
|
|||
extern uint64_t array_jobid(uint64_t);
|
||||
extern uint64_t array_idx(uint64_t);
|
||||
|
||||
/*
|
||||
* Bounded job-identity / element-selection parser (job-array-one-based-index,
|
||||
* Req 1.5, 5.7, 5.8, 5.12, 5.14, 5.15, 8.1).
|
||||
*
|
||||
* Grammar: optional leading/trailing spaces or tabs, then
|
||||
* `num-num-num-num` (cluster-apiserver-timestamp-sequence), optionally
|
||||
* followed by `[num]` or `[num-num]`. `num` is one or more decimal digits
|
||||
* (leading zeros allowed, no sign, no inner whitespace).
|
||||
*
|
||||
* Checks, in this order; only the first failure is reported:
|
||||
* JOBSEL_ESYNTAX grammar (5.15)
|
||||
* JOBSEL_EWIDTH a field wider than its bit width (5.14)
|
||||
* JOBSEL_EALIGN element part present but sequence % 32768 != 0 (5.8)
|
||||
* JOBSEL_EINDEX an element number above JOBID_FIELD_MAX_INDEX (5.7)
|
||||
* JOBSEL_EREVERSED range whose last number is below its first (5.5)
|
||||
* Digits are accumulated with an overflow guard, so oversized numbers fail
|
||||
* the width or index check instead of wrapping.
|
||||
*
|
||||
* On JOBSEL_OK: BARE -> `key` is the exact untagged ID, lo == hi == 0;
|
||||
* SINGLE -> `key` is the array ID (tag 0, low 15 bits clear), lo == hi == k;
|
||||
* RANGE -> `key` is the array ID, [lo, hi]. A NULL `text` is
|
||||
* JOBSEL_ESYNTAX; `out` must not be NULL.
|
||||
*/
|
||||
#define JOBID_FIELD_MAX_CLUSTER MAX_CLUSTER_ID
|
||||
#define JOBID_FIELD_MAX_APISERVER MAX_APISERVER_ID
|
||||
#define JOBID_FIELD_MAX_TIMESTAMP (UMAX_BITS(N_TIMESTAMP_BITS))
|
||||
#define JOBID_FIELD_MAX_SEQUENCE (UMAX_BITS(N_SEQUENCE_BITS))
|
||||
#define JOBID_FIELD_MAX_INDEX MAX_ARRAY_IDX
|
||||
|
||||
typedef enum {
|
||||
JOBSEL_OK = 0,
|
||||
JOBSEL_ESYNTAX, /* 5.15 */
|
||||
JOBSEL_EWIDTH, /* 5.14 */
|
||||
JOBSEL_EALIGN, /* 5.8 */
|
||||
JOBSEL_EINDEX, /* 5.7 */
|
||||
JOBSEL_EREVERSED /* 5.5 */
|
||||
} jobsel_err_t;
|
||||
|
||||
typedef enum {
|
||||
JOBSEL_BARE = 0,
|
||||
JOBSEL_SINGLE,
|
||||
JOBSEL_RANGE
|
||||
} jobsel_form_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t key; /* BARE: the exact (untagged) ID; else the array ID */
|
||||
jobsel_form_t form;
|
||||
uint32_t lo; /* SINGLE: lo == hi == k */
|
||||
uint32_t hi;
|
||||
} jobsel_t;
|
||||
|
||||
extern jobsel_err_t parse_jobsel(const char* text, jobsel_t* out);
|
||||
extern const char* jobsel_strerror(jobsel_err_t err);
|
||||
/* Non-zero iff the ID's tag equals TAG_JOB_ARRAY (tags 2 and 3 are not). */
|
||||
extern int is_array_tag(uint64_t jobid);
|
||||
/* Element ID of number k (masked to 15 bits) in the array `s->key`; 0 if
|
||||
* `s` is NULL. */
|
||||
extern uint64_t jobsel_element_id(const jobsel_t* s, uint32_t k);
|
||||
|
||||
/*
|
||||
* xce_units_mode_t / xce_format_bytes_human() / xce_is_bytes_valued_resource()
|
||||
* / xce_format_units_value() -- scheduler-sim-diagnostics spec, task 7 (Req
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ libxce_test_src = ['testsuite/test.c',
|
|||
'testsuite/test_zarray.c',
|
||||
'testsuite/test_config.c',
|
||||
'testsuite/test_idgen.c',
|
||||
'testsuite/test_jobsel.c',
|
||||
'testsuite/test_delay.c',
|
||||
'testsuite/test_ordq.c',
|
||||
'testsuite/test_preason.c',
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ extern void test_error(void);
|
|||
extern void test_utils(void);
|
||||
extern void test_config(void);
|
||||
extern void test_idgen(void);
|
||||
extern void test_jobsel(void);
|
||||
extern void test_preason(void);
|
||||
extern void test_ordq(void);
|
||||
extern void test_delay(void);
|
||||
|
|
@ -136,6 +137,7 @@ int main(int argc, char* argv[])
|
|||
UNITY_BEGIN();
|
||||
TEST(test_config);
|
||||
TEST(test_idgen);
|
||||
TEST(test_jobsel);
|
||||
TEST(test_ht_string);
|
||||
TEST(test_ht_int);
|
||||
TEST(test_zarray);
|
||||
|
|
|
|||
270
src/lib/testsuite/test_jobsel.c
Normal file
270
src/lib/testsuite/test_jobsel.c
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
/*
|
||||
* test_jobsel.c -- job-array-one-based-index, task 1.1.
|
||||
*
|
||||
* Runs the identity rows of the shared `selection-cases` conformance table
|
||||
* (design.md, Testing Strategy) against parse_jobsel(), plus the helpers
|
||||
* is_array_tag(), jobsel_element_id() and jobsel_strerror().
|
||||
*
|
||||
* The C surface has no decimal keys and no name selections, so the 5.17 and
|
||||
* name rows of the shared table do not apply here. `X` below stands for a
|
||||
* valid, array-capable written identity (`3-5-1700000000-65536`: sequence is
|
||||
* a multiple of 32768).
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define X_TXT "3-5-1700000000-65536"
|
||||
|
||||
/* MAKE_JOBID(3, 5, 1700000000, 0, 65536), the array ID / bare key of X. */
|
||||
#define X_KEY MAKE_JOBID(3, 5, 1700000000u, 0x0, 65536)
|
||||
|
||||
typedef struct {
|
||||
const char* text;
|
||||
jobsel_err_t err;
|
||||
jobsel_form_t form; /* checked on JOBSEL_OK only */
|
||||
uint64_t key; /* checked on JOBSEL_OK only */
|
||||
uint32_t lo;
|
||||
uint32_t hi;
|
||||
} jobsel_case_t;
|
||||
|
||||
/* clang-format off */
|
||||
static const jobsel_case_t selection_cases[] = {
|
||||
/* Valid */
|
||||
{ X_TXT, JOBSEL_OK, JOBSEL_BARE, X_KEY, 0, 0 },
|
||||
{ X_TXT "[1]", JOBSEL_OK, JOBSEL_SINGLE, X_KEY, 1, 1 },
|
||||
{ X_TXT "[0]", JOBSEL_OK, JOBSEL_SINGLE, X_KEY, 0, 0 }, /* 8.1 */
|
||||
{ X_TXT "[01]", JOBSEL_OK, JOBSEL_SINGLE, X_KEY, 1, 1 }, /* 1.5 */
|
||||
{ X_TXT "[1-3]", JOBSEL_OK, JOBSEL_RANGE, X_KEY, 1, 3 },
|
||||
{ X_TXT "[3-3]", JOBSEL_OK, JOBSEL_RANGE, X_KEY, 3, 3 },
|
||||
{ X_TXT "[32767]", JOBSEL_OK, JOBSEL_SINGLE, X_KEY, 32767, 32767 },
|
||||
{ X_TXT "[001-0003]", JOBSEL_OK, JOBSEL_RANGE, X_KEY, 1, 3 },
|
||||
/* surrounding spaces/tabs are ignored */
|
||||
{ " \t" X_TXT "[2] \t", JOBSEL_OK, JOBSEL_SINGLE, X_KEY, 2, 2 },
|
||||
{ " " X_TXT " ", JOBSEL_OK, JOBSEL_BARE, X_KEY, 0, 0 },
|
||||
/* leading zeros in identity fields are the same number */
|
||||
{ "03-05-01700000000-065536[2]", JOBSEL_OK, JOBSEL_SINGLE, X_KEY, 2, 2 },
|
||||
/* bare ordinary identity: no alignment requirement */
|
||||
{ "3-5-1700000000-65541", JOBSEL_OK, JOBSEL_BARE,
|
||||
MAKE_JOBID(3, 5, 1700000000u, 0x0, 65541), 0, 0 },
|
||||
/* every field at its maximum width */
|
||||
{ "63-15-4294967295-1048575", JOBSEL_OK, JOBSEL_BARE,
|
||||
MAKE_JOBID(63, 15, 4294967295u, 0x0, 1048575), 0, 0 },
|
||||
{ "63-15-4294967295-1015808[32767]", JOBSEL_OK, JOBSEL_SINGLE,
|
||||
MAKE_JOBID(63, 15, 4294967295u, 0x0, 1015808), 32767, 32767 },
|
||||
{ "0-0-0-0[0-32767]", JOBSEL_OK, JOBSEL_RANGE, 0, 0, 32767 },
|
||||
|
||||
/* Syntax (5.15) */
|
||||
{ X_TXT " [1]", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ X_TXT "[a]", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ X_TXT "[]", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ X_TXT "[1", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ X_TXT "]", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ X_TXT "[+1]", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ X_TXT "[-1]", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ X_TXT "[ 1]", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ X_TXT "[1 ]", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ X_TXT "[1-3:2]", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ X_TXT "[1]x", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ X_TXT "[1-2-3]", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ X_TXT "[1,3]", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ X_TXT "[1][2]", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ X_TXT "[1-]", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ X_TXT "[-3]", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ X_TXT "x", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ X_TXT "\n", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ "", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ " \t ", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ "[1]", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ "3-5-1700000000", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ "3-5-1700000000-65536-1", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ "-5-1700000000-65536", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ "3--1700000000-65536", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ "3-5-1700000000-", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ "+3-5-1700000000-65536", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ "3 -5-1700000000-65536", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ "3-5-17000 00000-65536", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ "123456", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
{ "foo[1]", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
/* syntax is checked before everything else, even huge fields */
|
||||
{ "99999999999999999999-0-0-0[1]x", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
|
||||
/* Widths (5.14) -- with and without an element part */
|
||||
{ "64-0-1700000000-32768[1]", JOBSEL_EWIDTH, 0, 0, 0, 0 },
|
||||
{ "0-16-1700000000-32768[1]", JOBSEL_EWIDTH, 0, 0, 0, 0 },
|
||||
{ "0-0-4294967296-32768[1]", JOBSEL_EWIDTH, 0, 0, 0, 0 },
|
||||
{ "0-0-1700000000-1048576[1]", JOBSEL_EWIDTH, 0, 0, 0, 0 },
|
||||
{ "64-0-1700000000-32768", JOBSEL_EWIDTH, 0, 0, 0, 0 },
|
||||
{ "0-16-1700000000-32768", JOBSEL_EWIDTH, 0, 0, 0, 0 },
|
||||
{ "0-0-4294967296-32768", JOBSEL_EWIDTH, 0, 0, 0, 0 },
|
||||
{ "0-0-1700000000-1048576", JOBSEL_EWIDTH, 0, 0, 0, 0 },
|
||||
/* too large to represent: must not wrap into a valid value */
|
||||
{ "18446744073709551616-0-0-0", JOBSEL_EWIDTH, 0, 0, 0, 0 },
|
||||
{ "0-0-0-99999999999999999999999", JOBSEL_EWIDTH, 0, 0, 0, 0 },
|
||||
{ "0-0-18446744073709551616-0[1]", JOBSEL_EWIDTH, 0, 0, 0, 0 },
|
||||
|
||||
/* Alignment (5.8) -- only when an element part is present */
|
||||
{ "3-5-1700000000-5[3]", JOBSEL_EALIGN, 0, 0, 0, 0 },
|
||||
{ "3-5-1700000000-65537[1-2]", JOBSEL_EALIGN, 0, 0, 0, 0 },
|
||||
{ "3-5-1700000000-32767[0]", JOBSEL_EALIGN, 0, 0, 0, 0 },
|
||||
|
||||
/* Index (5.7) */
|
||||
{ X_TXT "[32768]", JOBSEL_EINDEX, 0, 0, 0, 0 },
|
||||
{ X_TXT "[1-40000]", JOBSEL_EINDEX, 0, 0, 0, 0 },
|
||||
{ X_TXT "[40000-1]", JOBSEL_EINDEX, 0, 0, 0, 0 },
|
||||
{ X_TXT "[99999999999999999999]", JOBSEL_EINDEX, 0, 0, 0, 0 },
|
||||
{ X_TXT "[18446744073709551617]", JOBSEL_EINDEX, 0, 0, 0, 0 },
|
||||
{ X_TXT "[4294967297]", JOBSEL_EINDEX, 0, 0, 0, 0 },
|
||||
|
||||
/* Reversed (5.5) */
|
||||
{ X_TXT "[3-1]", JOBSEL_EREVERSED, 0, 0, 0, 0 },
|
||||
{ X_TXT "[32767-0]", JOBSEL_EREVERSED, 0, 0, 0, 0 },
|
||||
|
||||
/* Precedence (5.12): first failing check is the only one reported */
|
||||
{ "0-0-1700000000-1048576[40000]", JOBSEL_EWIDTH, 0, 0, 0, 0 },
|
||||
{ "3-5-1700000000-5[3-1]", JOBSEL_EALIGN, 0, 0, 0, 0 },
|
||||
{ "3-5-1700000000-5[40000]", JOBSEL_EALIGN, 0, 0, 0, 0 },
|
||||
{ "64-0-1700000000-5[3-1]", JOBSEL_EWIDTH, 0, 0, 0, 0 },
|
||||
{ "64-0-1700000000-5 [3-1]", JOBSEL_ESYNTAX, 0, 0, 0, 0 },
|
||||
};
|
||||
/* clang-format on */
|
||||
|
||||
static void test_jobsel_selection_cases(void)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < sizeof(selection_cases) / sizeof(selection_cases[0]); i++) {
|
||||
const jobsel_case_t* c = &selection_cases[i];
|
||||
jobsel_t sel;
|
||||
jobsel_err_t err;
|
||||
char msg[256];
|
||||
|
||||
memset(&sel, 0xa5, sizeof(sel));
|
||||
err = parse_jobsel(c->text, &sel);
|
||||
|
||||
snprintf(msg, sizeof(msg), "row %zu: \"%s\" (got %d, want %d)", i,
|
||||
c->text, (int)err, (int)c->err);
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(c->err, err, msg);
|
||||
|
||||
if (c->err != JOBSEL_OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(c->form, sel.form, msg);
|
||||
TEST_ASSERT_EQUAL_UINT64_MESSAGE(c->key, sel.key, msg);
|
||||
TEST_ASSERT_EQUAL_UINT32_MESSAGE(c->lo, sel.lo, msg);
|
||||
TEST_ASSERT_EQUAL_UINT32_MESSAGE(c->hi, sel.hi, msg);
|
||||
|
||||
if (sel.form != JOBSEL_BARE) {
|
||||
/* postcondition: array ID has tag 0 and low 15 bits clear */
|
||||
TEST_ASSERT_EQUAL_UINT64_MESSAGE(0, EXTRACT_ID_TAG(sel.key), msg);
|
||||
TEST_ASSERT_EQUAL_UINT64_MESSAGE(0, sel.key & MAX_ARRAY_IDX, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void test_jobsel_null(void)
|
||||
{
|
||||
jobsel_t sel;
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(JOBSEL_ESYNTAX, parse_jobsel(NULL, &sel));
|
||||
}
|
||||
|
||||
static void test_jobsel_field_max_constants(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_UINT64(63, JOBID_FIELD_MAX_CLUSTER);
|
||||
TEST_ASSERT_EQUAL_UINT64(15, JOBID_FIELD_MAX_APISERVER);
|
||||
TEST_ASSERT_EQUAL_UINT64(4294967295ull, JOBID_FIELD_MAX_TIMESTAMP);
|
||||
TEST_ASSERT_EQUAL_UINT64(1048575ull, JOBID_FIELD_MAX_SEQUENCE);
|
||||
TEST_ASSERT_EQUAL_UINT64(32767, JOBID_FIELD_MAX_INDEX);
|
||||
}
|
||||
|
||||
static void test_jobsel_is_array_tag(void)
|
||||
{
|
||||
uint64_t base = MAKE_JOBID(3, 5, 1700000000u, 0x0, 65536);
|
||||
|
||||
TEST_ASSERT_FALSE(is_array_tag(base));
|
||||
TEST_ASSERT_FALSE(is_array_tag(base | 7));
|
||||
TEST_ASSERT_TRUE(
|
||||
is_array_tag(base | ((uint64_t)TAG_JOB_ARRAY << TAG_SHIFT) | 1));
|
||||
TEST_ASSERT_TRUE(is_array_tag((uint64_t)TAG_JOB_ARRAY << TAG_SHIFT));
|
||||
/* tag equals the array tag only: tags 2 and 3 are not arrays */
|
||||
TEST_ASSERT_FALSE(is_array_tag(base | ((uint64_t)0x2 << TAG_SHIFT)));
|
||||
TEST_ASSERT_FALSE(is_array_tag(base | ((uint64_t)0x3 << TAG_SHIFT)));
|
||||
TEST_ASSERT_FALSE(is_array_tag(0));
|
||||
}
|
||||
|
||||
static void test_jobsel_element_id(void)
|
||||
{
|
||||
jobsel_t sel;
|
||||
uint32_t ks[] = { 0, 1, 2, 3, 32767 };
|
||||
size_t i;
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(JOBSEL_OK, parse_jobsel(X_TXT "[0-32767]", &sel));
|
||||
|
||||
for (i = 0; i < sizeof(ks) / sizeof(ks[0]); i++) {
|
||||
uint64_t elem = jobsel_element_id(&sel, ks[i]);
|
||||
char buf[128];
|
||||
char want[128];
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT64(
|
||||
MAKE_JOBID(3, 5, 1700000000u, TAG_JOB_ARRAY, 65536 | ks[i]), elem);
|
||||
TEST_ASSERT_TRUE(is_array_tag(elem));
|
||||
TEST_ASSERT_EQUAL_UINT64(sel.key, array_jobid(elem));
|
||||
TEST_ASSERT_EQUAL_UINT64(ks[i], array_idx(elem));
|
||||
|
||||
/* round trip through the display form */
|
||||
TEST_ASSERT_TRUE(fmt_jobid(buf, sizeof(buf), elem) > 0);
|
||||
snprintf(want, sizeof(want), "%s[%u]", X_TXT, ks[i]);
|
||||
TEST_ASSERT_EQUAL_STRING(want, buf);
|
||||
}
|
||||
|
||||
/* the widest identity composes without spilling into other fields */
|
||||
TEST_ASSERT_EQUAL_INT(
|
||||
JOBSEL_OK, parse_jobsel("63-15-4294967295-1015808[32767]", &sel));
|
||||
TEST_ASSERT_EQUAL_UINT64(
|
||||
MAKE_JOBID(63, 15, 4294967295u, TAG_JOB_ARRAY, 1015808 | 32767),
|
||||
jobsel_element_id(&sel, 32767));
|
||||
TEST_ASSERT_EQUAL_UINT64(sel.key,
|
||||
array_jobid(jobsel_element_id(&sel, 32767)));
|
||||
|
||||
/* k is masked to the 15-bit index field */
|
||||
TEST_ASSERT_EQUAL_UINT64(jobsel_element_id(&sel, 5),
|
||||
jobsel_element_id(&sel, 32768 + 5));
|
||||
TEST_ASSERT_EQUAL_UINT64(0, jobsel_element_id(NULL, 1));
|
||||
}
|
||||
|
||||
static void test_jobsel_strerror(void)
|
||||
{
|
||||
jobsel_err_t errs[] = { JOBSEL_OK, JOBSEL_ESYNTAX, JOBSEL_EWIDTH,
|
||||
JOBSEL_EALIGN, JOBSEL_EINDEX, JOBSEL_EREVERSED };
|
||||
size_t i, j;
|
||||
|
||||
for (i = 0; i < sizeof(errs) / sizeof(errs[0]); i++) {
|
||||
const char* m = jobsel_strerror(errs[i]);
|
||||
|
||||
TEST_ASSERT_NOT_NULL(m);
|
||||
TEST_ASSERT_TRUE(strlen(m) > 0);
|
||||
for (j = 0; j < i; j++) {
|
||||
TEST_ASSERT_TRUE(strcmp(m, jobsel_strerror(errs[j])) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_ASSERT_NOT_NULL(strstr(jobsel_strerror(JOBSEL_EINDEX), "32767"));
|
||||
TEST_ASSERT_NOT_NULL(strstr(jobsel_strerror(JOBSEL_EREVERSED), "revers"));
|
||||
TEST_ASSERT_NOT_NULL(jobsel_strerror((jobsel_err_t)99));
|
||||
}
|
||||
|
||||
void test_jobsel(void)
|
||||
{
|
||||
RUN_TEST(test_jobsel_selection_cases);
|
||||
RUN_TEST(test_jobsel_null);
|
||||
RUN_TEST(test_jobsel_field_max_constants);
|
||||
RUN_TEST(test_jobsel_is_array_tag);
|
||||
RUN_TEST(test_jobsel_element_id);
|
||||
RUN_TEST(test_jobsel_strerror);
|
||||
}
|
||||
168
src/lib/utils.c
168
src/lib/utils.c
|
|
@ -709,6 +709,174 @@ uint64_t array_idx(uint64_t jobid)
|
|||
return EXTRACT_ARRAY_IDX(jobid);
|
||||
}
|
||||
|
||||
/*
|
||||
* Scan one `num` (one or more decimal digits) at *pp. The value saturates at
|
||||
* UINT64_MAX instead of wrapping, so an oversized number always fails the
|
||||
* caller's width/index bound. Returns false if no digit is present.
|
||||
*/
|
||||
static bool jobsel_scan_num(const char** pp, uint64_t* val)
|
||||
{
|
||||
const char* p = *pp;
|
||||
uint64_t v = 0;
|
||||
|
||||
if ((*p < '0') || (*p > '9')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (; (*p >= '0') && (*p <= '9'); p++) {
|
||||
uint64_t d = (uint64_t)(*p - '0');
|
||||
|
||||
if (v > (UINT64_MAX - d) / 10) {
|
||||
v = UINT64_MAX;
|
||||
} else {
|
||||
v = v * 10 + d;
|
||||
}
|
||||
}
|
||||
|
||||
*pp = p;
|
||||
*val = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char* jobsel_skip_blank(const char* p)
|
||||
{
|
||||
while ((*p == ' ') || (*p == '\t')) {
|
||||
p++;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
jobsel_err_t parse_jobsel(const char* text, jobsel_t* out)
|
||||
{
|
||||
const char* p;
|
||||
uint64_t f[4];
|
||||
uint64_t lo = 0, hi = 0;
|
||||
int nidx = 0; /* 0: bare, 1: [k], 2: [a-b] */
|
||||
int i;
|
||||
|
||||
if (out != NULL) {
|
||||
memset(out, 0x0, sizeof(*out));
|
||||
}
|
||||
|
||||
if ((text == NULL) || (out == NULL)) {
|
||||
return JOBSEL_ESYNTAX;
|
||||
}
|
||||
|
||||
/* 1. syntax (5.15): the whole text is scanned before any value check */
|
||||
p = jobsel_skip_blank(text);
|
||||
for (i = 0; i < 4; i++) {
|
||||
if ((i > 0) && (*p++ != '-')) {
|
||||
return JOBSEL_ESYNTAX;
|
||||
}
|
||||
if (!jobsel_scan_num(&p, &f[i])) {
|
||||
return JOBSEL_ESYNTAX;
|
||||
}
|
||||
}
|
||||
|
||||
if (*p == '[') {
|
||||
p++;
|
||||
if (!jobsel_scan_num(&p, &lo)) {
|
||||
return JOBSEL_ESYNTAX;
|
||||
}
|
||||
hi = lo;
|
||||
nidx = 1;
|
||||
if (*p == '-') {
|
||||
p++;
|
||||
if (!jobsel_scan_num(&p, &hi)) {
|
||||
return JOBSEL_ESYNTAX;
|
||||
}
|
||||
nidx = 2;
|
||||
}
|
||||
if (*p++ != ']') {
|
||||
return JOBSEL_ESYNTAX;
|
||||
}
|
||||
}
|
||||
|
||||
p = jobsel_skip_blank(p);
|
||||
if (*p != '\0') {
|
||||
return JOBSEL_ESYNTAX;
|
||||
}
|
||||
|
||||
/* 2. field widths (5.14) */
|
||||
if ((f[0] > JOBID_FIELD_MAX_CLUSTER) || (f[1] > JOBID_FIELD_MAX_APISERVER)
|
||||
|| (f[2] > JOBID_FIELD_MAX_TIMESTAMP)
|
||||
|| (f[3] > JOBID_FIELD_MAX_SEQUENCE)) {
|
||||
return JOBSEL_EWIDTH;
|
||||
}
|
||||
|
||||
/* 3. alignment of the array identity when an element part is present
|
||||
* (5.8) */
|
||||
if ((nidx > 0) && ((f[3] & MAX_ARRAY_IDX) != 0)) {
|
||||
return JOBSEL_EALIGN;
|
||||
}
|
||||
|
||||
/* 4. element-number bound (5.7) */
|
||||
if ((nidx > 0)
|
||||
&& ((lo > JOBID_FIELD_MAX_INDEX) || (hi > JOBID_FIELD_MAX_INDEX))) {
|
||||
return JOBSEL_EINDEX;
|
||||
}
|
||||
|
||||
/* 5. reversed range (5.5) */
|
||||
if (hi < lo) {
|
||||
return JOBSEL_EREVERSED;
|
||||
}
|
||||
|
||||
/* All fields are within their widths here, so composition cannot spill
|
||||
* into a neighbouring field. */
|
||||
out->key = MAKE_JOBID(f[0], f[1], f[2], 0x0, f[3]);
|
||||
out->form = (nidx == 0) ? JOBSEL_BARE
|
||||
: (nidx == 1) ? JOBSEL_SINGLE
|
||||
: JOBSEL_RANGE;
|
||||
out->lo = (uint32_t)lo;
|
||||
out->hi = (uint32_t)hi;
|
||||
|
||||
return JOBSEL_OK;
|
||||
}
|
||||
|
||||
const char* jobsel_strerror(jobsel_err_t err)
|
||||
{
|
||||
switch (err) {
|
||||
case JOBSEL_OK:
|
||||
return "valid job identity";
|
||||
case JOBSEL_ESYNTAX:
|
||||
return "invalid job identity: expected "
|
||||
"<cluster>-<apiserver>-<timestamp>-<sequence> optionally "
|
||||
"followed by [k] or [a-b] (decimal digits only, no sign or "
|
||||
"whitespace)";
|
||||
case JOBSEL_EWIDTH:
|
||||
return "invalid job identity: a field is out of range (cluster <= 63, "
|
||||
"apiserver <= 15, timestamp < 2^32, sequence < 2^20)";
|
||||
case JOBSEL_EALIGN:
|
||||
return "invalid job identity: not an array identity";
|
||||
case JOBSEL_EINDEX:
|
||||
return "invalid element selection: element numbers must be at most "
|
||||
"32767";
|
||||
case JOBSEL_EREVERSED:
|
||||
return "invalid element selection: the range is reversed";
|
||||
}
|
||||
|
||||
return "invalid job identity";
|
||||
}
|
||||
|
||||
int is_array_tag(uint64_t jobid)
|
||||
{
|
||||
return EXTRACT_ID_TAG(jobid) == TAG_JOB_ARRAY;
|
||||
}
|
||||
|
||||
uint64_t jobsel_element_id(const jobsel_t* s, uint32_t k)
|
||||
{
|
||||
uint64_t key;
|
||||
|
||||
if (s == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
key = s->key;
|
||||
return MAKE_JOBID(EXTRACT_ID_CLUSTER(key), EXTRACT_ID_APISERVER(key),
|
||||
EXTRACT_ID_TIMESTAMP(key), TAG_JOB_ARRAY,
|
||||
EXTRACT_ARRAY_JOB(key) | (k & MAX_ARRAY_IDX));
|
||||
}
|
||||
|
||||
/*
|
||||
* xce_format_bytes_human / xce_is_bytes_valued_resource / xce_format_units_value
|
||||
* (scheduler-sim-diagnostics spec, task 7, Requirement 6.1/6.3/6.5) --
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue