feat(job-array-one-based-index): XCE_JOBINDEX is the element number (task 3.2)

The worker sets XCE_JOBINDEX to the element number for jobs carrying the
array marking (a one-element array's element gets 1) and to 0 otherwise.
A submission-supplied XCE_JOBINDEX is not applied and a WARN names the
job and the ignored variable; XCE_JOBID handling is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-01 10:34:59 +00:00
parent c60946c097
commit 7aa00f0d9d
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
4 changed files with 269 additions and 4 deletions

View file

@ -59,7 +59,7 @@
- _Boundary: SchedGuard_
- _Depends: 1.1_
- [ ] 3.2 (P) Expose the element number to running jobs
- [x] 3.2 (P) Expose the element number to running jobs
- Set `XCE_JOBINDEX` to the element number for jobs carrying the array marking and to 0 otherwise
- Skip a submission-supplied `XCE_JOBINDEX`, logging a WARN that names the job and the ignored variable; leave `XCE_JOBID` handling unchanged
- Add the worker test and register it in the worker test build

View file

@ -416,8 +416,11 @@ void apply_env_vars(env_var_t** env_vars, uint64_t jobid)
jobid_str);
}
char jobindex_str[8];
snprintf(jobindex_str, sizeof(jobindex_str), "%d", 0);
/* Element number of an array element (6.1, 7.8); 0 otherwise (6.2). */
uint64_t jobindex = is_array_tag(jobid) ? array_idx(jobid) : 0;
char jobindex_str[8];
snprintf(jobindex_str, sizeof(jobindex_str), "%lu",
(unsigned long)jobindex);
if (setenv("XCE_JOBINDEX", jobindex_str, 1) != 0) {
XCECD_WORKER_LOG(XCE_LOG_ERROR,
"Error setting environment variable: %s",
@ -431,7 +434,16 @@ void apply_env_vars(env_var_t** env_vars, uint64_t jobid)
while (current != NULL) {
next_node = current->next;
if (current->key && current->value) {
if (current->key && current->value
&& strcmp(current->key, "XCE_JOBINDEX") == 0) {
/* The system value cannot be overridden (6.3, 6.4). */
char idstr[64];
fmt_jobid(idstr, sizeof(idstr), jobid);
XCECD_WORKER_LOG(XCE_LOG_WARN,
"Job %s: ignoring submitted environment "
"variable XCE_JOBINDEX (set by XCE)",
idstr);
} else if (current->key && current->value) {
// Set environment variable (overwrite if exists)
if (setenv(current->key, current->value, 1) != 0) {
XCECD_WORKER_LOG(XCE_LOG_ERROR,

View file

@ -219,3 +219,16 @@ test('worker diskstats test', diskstats_test,
suite: 'worker',
timeout: 60,
env: ['ASAN_OPTIONS=detect_leaks=0'])
# job-array-one-based-index task 3.2 (Requirements 6.1-6.5, 7.8): XCE_JOBINDEX
# in a running job's environment -- white-box #include of xcecd_exdata.c.
exdata_jobindex_test = executable('exdata_jobindex_test',
'test_exdata_jobindex.c',
include_directories: [worker_test_incdir],
dependencies: worker_test_deps,
install: false)
test('worker exdata_jobindex test', exdata_jobindex_test,
suite: 'worker',
timeout: 60,
env: ['ASAN_OPTIONS=detect_leaks=0'])

View file

@ -0,0 +1,240 @@
/*
* test_exdata_jobindex.c -- XCE_JOBINDEX in a running job's environment
* (job-array-one-based-index, task 3.2; Requirements 6.1-6.5, 7.8).
*
* apply_env_vars() (xcecd_exdata.c) sets XCE_JOBID and XCE_JOBINDEX and then
* the submission's own variables. XCE_JOBINDEX must be the element number of
* an array-tagged job ID (a one-element array included) and 0 for an
* ordinary job; a submission-supplied XCE_JOBINDEX is ignored with a WARN
* naming the job and the variable; XCE_JOBID handling is unchanged.
*
* White-box #include of ../src/xcecd_exdata.c (same convention as the other
* tests in this suite). XCECD_WORKER_LOG is redefined to a capture hook
* before the include: xcelog.h is include-guarded, so the production file's
* own #include "xcelog.h" does not restore the original macro.
*/
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unity/unity.h>
#include "config.h"
#include "utils.h"
#include "xcelog.h"
#define CAPTURE_MAX 32
static int captured_level[CAPTURE_MAX];
static char captured_msg[CAPTURE_MAX][512];
static int captured_count;
static void capture_log(int level, const char* fmt, ...)
{
va_list ap;
if (captured_count >= CAPTURE_MAX) {
return;
}
captured_level[captured_count] = level;
va_start(ap, fmt);
vsnprintf(captured_msg[captured_count],
sizeof(captured_msg[captured_count]), fmt, ap);
va_end(ap);
captured_count++;
}
#undef XCECD_WORKER_LOG
#define XCECD_WORKER_LOG(level, format, ...) \
capture_log((int)(level), format __VA_OPT__(, ) __VA_ARGS__)
#include "../src/xcecd_exdata.c"
/* cluster 1, apiserver 2, timestamp 3, sequence block 1 (seq 1 << 15) */
#define TEST_SEQ_BLOCK ((uint64_t)1 << N_ARRAY_IDX_BITS)
#define ARRAY_ELEMENT(k) \
MAKE_JOBID(1, 2, 3, TAG_JOB_ARRAY, TEST_SEQ_BLOCK | (uint64_t)(k))
#define ORDINARY_JOB MAKE_JOBID(1, 2, 3, 0, 77)
static env_var_t* push_var(env_var_t* head, const char* key, const char* value)
{
env_var_t* node = calloc(1, sizeof(*node));
TEST_ASSERT_NOT_NULL(node);
node->key = strdup(key);
node->value = strdup(value);
node->next = head;
return node;
}
/* Index of the first captured WARN containing every needle, or -1. */
static int find_warn(const char* needle1, const char* needle2)
{
for (int i = 0; i < captured_count; i++) {
if (captured_level[i] == XCE_LOG_WARN
&& strstr(captured_msg[i], needle1) != NULL
&& strstr(captured_msg[i], needle2) != NULL) {
return i;
}
}
return -1;
}
static int count_warns(void)
{
int n = 0;
for (int i = 0; i < captured_count; i++) {
if (captured_level[i] == XCE_LOG_WARN) {
n++;
}
}
return n;
}
static void expect_env(const char* name, const char* value)
{
const char* got = getenv(name);
TEST_ASSERT_NOT_NULL_MESSAGE(got, name);
TEST_ASSERT_EQUAL_STRING_MESSAGE(value, got, name);
}
void setUp(void)
{
captured_count = 0;
unsetenv("XCE_JOBID");
unsetenv("XCE_JOBINDEX");
unsetenv("XCE_TEST_USERVAR");
}
void tearDown(void) { return; }
/* 6.1: an array element sees its own element number. */
static void test_array_element_gets_its_number(void)
{
env_var_t* vars = NULL;
apply_env_vars(&vars, ARRAY_ELEMENT(3));
expect_env("XCE_JOBINDEX", "3");
apply_env_vars(&vars, ARRAY_ELEMENT(32767));
expect_env("XCE_JOBINDEX", "32767");
}
/* 7.8: a one-element array's element behaves like any element: number 1. */
static void test_one_element_array_gets_one(void)
{
env_var_t* vars = NULL;
apply_env_vars(&vars, ARRAY_ELEMENT(1));
expect_env("XCE_JOBINDEX", "1");
}
/* 6.2: an ordinary job gets 0 even if its low bits are non-zero. */
static void test_ordinary_job_gets_zero(void)
{
env_var_t* vars = NULL;
apply_env_vars(&vars, ORDINARY_JOB);
expect_env("XCE_JOBINDEX", "0");
}
/* A pre-upgrade element 0 (tagged, index 0) is still reported as 0. */
static void test_preupgrade_element_zero_gets_zero(void)
{
env_var_t* vars = NULL;
apply_env_vars(&vars, ARRAY_ELEMENT(0));
expect_env("XCE_JOBINDEX", "0");
}
/* 6.3, 6.4: a submission's XCE_JOBINDEX is ignored with a WARN naming the
* job and the variable; other user variables are still applied. */
static void test_user_jobindex_ignored_with_warn(void)
{
env_var_t* vars = NULL;
char idstr[64];
vars = push_var(vars, "XCE_TEST_USERVAR", "kept");
vars = push_var(vars, "XCE_JOBINDEX", "99");
apply_env_vars(&vars, ARRAY_ELEMENT(5));
TEST_ASSERT_NULL(vars);
expect_env("XCE_JOBINDEX", "5");
expect_env("XCE_TEST_USERVAR", "kept");
fmt_jobid(idstr, sizeof(idstr), ARRAY_ELEMENT(5));
TEST_ASSERT_TRUE_MESSAGE(find_warn(idstr, "XCE_JOBINDEX") >= 0,
"no WARN naming the job and XCE_JOBINDEX");
TEST_ASSERT_EQUAL_INT(1, count_warns());
}
/* 6.3: an ordinary job's user XCE_JOBINDEX is ignored too (stays 0). */
static void test_user_jobindex_ignored_for_ordinary_job(void)
{
env_var_t* vars = NULL;
char idstr[64];
vars = push_var(vars, "XCE_JOBINDEX", "");
apply_env_vars(&vars, ORDINARY_JOB);
expect_env("XCE_JOBINDEX", "0");
fmt_jobid(idstr, sizeof(idstr), ORDINARY_JOB);
TEST_ASSERT_TRUE(find_warn(idstr, "XCE_JOBINDEX") >= 0);
}
/* Only the exact name is ignored. */
static void test_similar_names_still_applied(void)
{
env_var_t* vars = NULL;
vars = push_var(vars, "XCE_JOBINDEX_X", "a");
vars = push_var(vars, "xce_jobindex", "b");
apply_env_vars(&vars, ARRAY_ELEMENT(2));
expect_env("XCE_JOBINDEX", "2");
expect_env("XCE_JOBINDEX_X", "a");
expect_env("xce_jobindex", "b");
TEST_ASSERT_EQUAL_INT(0, count_warns());
unsetenv("XCE_JOBINDEX_X");
unsetenv("xce_jobindex");
}
/* 6.5: XCE_JOBID is the decimal job ID; a submission's own XCE_JOBID still
* overrides it, as before, with no WARN. */
static void test_jobid_unchanged(void)
{
env_var_t* vars = NULL;
char dec[32];
snprintf(dec, sizeof(dec), "%lu", (unsigned long)ARRAY_ELEMENT(4));
apply_env_vars(&vars, ARRAY_ELEMENT(4));
expect_env("XCE_JOBID", dec);
snprintf(dec, sizeof(dec), "%lu", (unsigned long)ORDINARY_JOB);
apply_env_vars(&vars, ORDINARY_JOB);
expect_env("XCE_JOBID", dec);
captured_count = 0;
vars = push_var(vars, "XCE_JOBID", "user-value");
apply_env_vars(&vars, ORDINARY_JOB);
expect_env("XCE_JOBID", "user-value");
TEST_ASSERT_EQUAL_INT(0, count_warns());
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_array_element_gets_its_number);
RUN_TEST(test_one_element_array_gets_one);
RUN_TEST(test_ordinary_job_gets_zero);
RUN_TEST(test_preupgrade_element_zero_gets_zero);
RUN_TEST(test_user_jobindex_ignored_with_warn);
RUN_TEST(test_user_jobindex_ignored_for_ordinary_job);
RUN_TEST(test_similar_names_still_applied);
RUN_TEST(test_jobid_unchanged);
return UNITY_END();
}