feat(scheduler-sim-decision-trace): manage the trace file copy and output-file conflicts
Task 4.5: a named trace file is opened for appending at command time; an open failure or a path that is the sim output file or the scheduler log (same device and inode) is rejected with settings unchanged; files close when their last settings holder releases them; set output gets a conflict check; shutdown flushes and closes every trace file and frees the store. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e01ffe4c9c
commit
27cad0b0b7
6 changed files with 786 additions and 34 deletions
|
|
@ -231,7 +231,7 @@
|
|||
- _Requirements: 8.3_
|
||||
- _Boundary: SimIntegration (driver arguments)_
|
||||
|
||||
- [ ] 4.5 Manage the trace file copy and output-file conflicts, test first
|
||||
- [x] 4.5 Manage the trace file copy and output-file conflicts, test first
|
||||
- When a command names a file, it is opened for appending at once. An open failure names the file and the cause, and the settings stay unchanged.
|
||||
- A path that is the same file (device and inode) as the current simulation output file or the scheduler log is rejected.
|
||||
- Expose a conflict check, so `set output` can reject any trace file that is still open.
|
||||
|
|
@ -368,3 +368,4 @@
|
|||
- 4.2: the store API is `repl_trace_store_put`, `repl_trace_store_bytes`, `repl_cmd_info_trace` and `repl_trace_store_fini` (an addition; nothing calls it at shutdown yet, so 4.5/4.6/5.2 must). `xce_repl_trace_state_t` (repl_internal.h) holds only the store fields; 4.3 adds next/cur/session flags to it. The 1.3 "file tag" is dropped: `info trace` is console-only (Req 8.1) and emission writes through the session's `cur` settings. `info trace` is not yet routed from repl_cmd_info (5.2). Open (6.2): an `id_list_add` OOM can silently drop array elements from a listing.
|
||||
- 4.3: `trace` is parsed from the raw line by repl_trace.c. The settings are a refcounted REPL-side wrapper around xce_sched_trace_ctl_t (trace.h untouched), held as `next`/`cur` in xce_repl_trace_state_t together with session_in_progress, round_traced and round_flushed. The 1.3 bare-id item is settled: an array-aligned bare id adds both an exact and a whole-array selector, so later elements match; status shows that as `<base>[*]`. A range expands to one exact selector per element. The wrapper frees `ctl.policy_name` on its last release, so 4.6 must heap-allocate it. Nothing frees next/cur at REPL shutdown yet, so 4.5/4.6 must add the shutdown path; routing `trace` from repl.c is 5.2.
|
||||
- 4.4: the driver holds its own copy of the scheduler log path in `sim->log_path` (from `sched_sim_args_t.log_path`, else `SCHED_DEFAULT_LOG_PATH` in sim.h, "/tmp/xce_scheduler.log"). The log is always open in sim mode, so 4.5 can `stat(sim->log_path)` for its dev/ino same-file check.
|
||||
- 4.5: the trace FILE* and its dev/ino belong to the refcounted settings, and the last unref flushes and closes it. New functions in repl_internal.h: `repl_trace_settings_file()`, `repl_trace_file_conflicts(sim, path)` (5.2 wires it into `set output`) and `repl_trace_shutdown(sim)` (5.2 calls it on quit/EOF). Shutdown clears core.trace_ctl, releases cur/next, then calls store_fini, which memsets the trace state, so keep that order. 4.6 must emit only through `cur`'s file while it still holds `cur`, and flush after every record, because two append streams may share one file.
|
||||
|
|
|
|||
|
|
@ -647,4 +647,29 @@ repl_trace_settings_ctl(xce_repl_trace_settings_t* s);
|
|||
extern const char*
|
||||
repl_trace_settings_path(const xce_repl_trace_settings_t* s);
|
||||
|
||||
/*
|
||||
* Trace file copy (repl_trace.c, task 4.5, design.md "ReplTraceControl"
|
||||
* file rules; Requirements 8.2-8.7):
|
||||
*
|
||||
* repl_trace_settings_file - the settings object's trace file, opened for
|
||||
* appending when `trace on ... file PATH` ran (8.2), or NULL. The FILE*
|
||||
* belongs to the settings object: it is flushed and closed by the last
|
||||
* repl_trace_settings_unref(), so a file replaced or disabled while a
|
||||
* session holds `cur` stays open until that session ends, and is closed at
|
||||
* once otherwise (8.5).
|
||||
*
|
||||
* repl_trace_file_conflicts - true when `path` names, by device and inode,
|
||||
* the file of `next` or `cur` (every trace file still open). `set output`
|
||||
* rejects such a path (8.4). False for a NULL or nonexistent path.
|
||||
*
|
||||
* repl_trace_shutdown - for quit, end of input and replay completion:
|
||||
* withdraws any published control block, releases `next` and `cur`
|
||||
* (flushing and closing their files, 8.6), clears the session flags and
|
||||
* frees the stored traces. NULL-safe and idempotent.
|
||||
*/
|
||||
extern FILE* repl_trace_settings_file(const xce_repl_trace_settings_t* s);
|
||||
extern bool repl_trace_file_conflicts(const xce_sched_sim_driver_t* sim,
|
||||
const char* path);
|
||||
extern void repl_trace_shutdown(xce_sched_sim_driver_t* sim);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -25,15 +25,25 @@
|
|||
* started with). Commands build a complete new object and only then
|
||||
* replace `next`, so an invalid argument leaves everything unchanged
|
||||
* (1.9), and a session in progress keeps its own settings (1.6). The
|
||||
* session hooks that move `next` into `cur` are task 4.6; opening the file
|
||||
* and the same-file checks are task 4.5 (only the path is kept here).
|
||||
* session hooks that move `next` into `cur` are task 4.6.
|
||||
*
|
||||
* Trace file (task 4.5, 8.2-8.7): a named file is opened for appending
|
||||
* when the command runs, after a stat() check that it is not the
|
||||
* simulation output file or the scheduler log (by device and inode, so a
|
||||
* hard link or symlink is caught too). The FILE* belongs to the settings
|
||||
* object and is flushed and closed by its last unref, so a file replaced
|
||||
* or disabled during a session stays open until that session drops `cur`,
|
||||
* and is closed at once otherwise. repl_trace_shutdown() releases both
|
||||
* references and the store at REPL exit.
|
||||
*
|
||||
* Runs on the sim thread at the REPL prompt only; writes nothing through
|
||||
* tlog (8.7).
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "repl_internal.h"
|
||||
#include "sched/trace.h"
|
||||
|
|
@ -46,6 +56,9 @@ struct xce_repl_trace_settings_s {
|
|||
int refs;
|
||||
xce_sched_trace_ctl_t ctl; /* ctl.sel and ctl.policy_name owned here */
|
||||
char* path; /* trace file path, NULL when none */
|
||||
FILE* file; /* open for appending when path is set */
|
||||
dev_t dev; /* file's device and inode, for the */
|
||||
ino_t ino; /* same-file checks (8.3, 8.4) */
|
||||
int cap; /* allocated length of ctl.sel */
|
||||
};
|
||||
|
||||
|
|
@ -78,6 +91,10 @@ void repl_trace_settings_unref(xce_repl_trace_settings_t* s)
|
|||
if ((s == NULL) || (--s->refs > 0)) {
|
||||
return;
|
||||
}
|
||||
if (s->file != NULL) {
|
||||
fflush(s->file); /* 8.6: every printed trace reaches the file */
|
||||
fclose(s->file);
|
||||
}
|
||||
free(s->ctl.sel);
|
||||
free((char*)s->ctl.policy_name);
|
||||
free(s->path);
|
||||
|
|
@ -94,6 +111,11 @@ const char* repl_trace_settings_path(const xce_repl_trace_settings_t* s)
|
|||
return (s != NULL) ? s->path : NULL;
|
||||
}
|
||||
|
||||
FILE* repl_trace_settings_file(const xce_repl_trace_settings_t* s)
|
||||
{
|
||||
return (s != NULL) ? s->file : NULL;
|
||||
}
|
||||
|
||||
static bool sel_add(xce_repl_trace_settings_t* s, uint64_t key,
|
||||
bool whole_array)
|
||||
{
|
||||
|
|
@ -350,6 +372,112 @@ fail:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/* ---- the trace file (task 4.5) ------------------------------------------ */
|
||||
|
||||
static bool same_file(const struct stat* a, const struct stat* b)
|
||||
{
|
||||
return (a->st_dev == b->st_dev) && (a->st_ino == b->st_ino);
|
||||
}
|
||||
|
||||
/* 8.3: rejects a path that is the simulation output file in effect or the
|
||||
* scheduler log. A path that does not exist yet is neither; any other
|
||||
* stat() failure is left for fopen() to report. */
|
||||
static bool reject_reserved_file(const xce_sched_sim_driver_t* sim,
|
||||
const char* path)
|
||||
{
|
||||
struct stat want;
|
||||
struct stat st;
|
||||
|
||||
if (stat(path, &want) != 0) {
|
||||
return false;
|
||||
}
|
||||
if ((sim->record_file != NULL) &&
|
||||
(fstat(fileno(sim->record_file), &st) == 0) && same_file(&want, &st)) {
|
||||
fprintf(stdout,
|
||||
"[sim] trace: file %s is the simulation output file; "
|
||||
"choose another file\n",
|
||||
path);
|
||||
return true;
|
||||
}
|
||||
if ((sim->log_path != NULL) && (stat(sim->log_path, &st) == 0) &&
|
||||
same_file(&want, &st)) {
|
||||
fprintf(stdout,
|
||||
"[sim] trace: file %s is the scheduler log file; choose "
|
||||
"another file\n",
|
||||
path);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 8.2: opens s->path for appending now, so an error is reported while the
|
||||
* old settings are still in place. Returns false after printing it. */
|
||||
static bool open_file(const xce_sched_sim_driver_t* sim,
|
||||
xce_repl_trace_settings_t* s)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
if (reject_reserved_file(sim, s->path)) {
|
||||
return false;
|
||||
}
|
||||
s->file = fopen(s->path, "a");
|
||||
if (s->file == NULL) {
|
||||
fprintf(stdout, "[sim] trace: cannot open file %s: %s\n", s->path,
|
||||
strerror(errno));
|
||||
return false;
|
||||
}
|
||||
if (fstat(fileno(s->file), &st) != 0) {
|
||||
fprintf(stdout, "[sim] trace: cannot open file %s: %s\n", s->path,
|
||||
strerror(errno));
|
||||
fclose(s->file);
|
||||
s->file = NULL;
|
||||
return false;
|
||||
}
|
||||
s->dev = st.st_dev;
|
||||
s->ino = st.st_ino;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool settings_hold(const xce_repl_trace_settings_t* s,
|
||||
const struct stat* st)
|
||||
{
|
||||
return (s != NULL) && (s->file != NULL) && (s->dev == st->st_dev) &&
|
||||
(s->ino == st->st_ino);
|
||||
}
|
||||
|
||||
bool repl_trace_file_conflicts(const xce_sched_sim_driver_t* sim,
|
||||
const char* path)
|
||||
{
|
||||
const xce_repl_trace_state_t* ts;
|
||||
struct stat st;
|
||||
|
||||
if ((sim == NULL) || (sim->repl_state == NULL) || (path == NULL) ||
|
||||
(stat(path, &st) != 0)) {
|
||||
return false;
|
||||
}
|
||||
ts = &(sim->repl_state->trace);
|
||||
return settings_hold(ts->next, &st) || settings_hold(ts->cur, &st);
|
||||
}
|
||||
|
||||
void repl_trace_shutdown(xce_sched_sim_driver_t* sim)
|
||||
{
|
||||
xce_repl_trace_state_t* ts;
|
||||
|
||||
if ((sim == NULL) || (sim->repl_state == NULL)) {
|
||||
return;
|
||||
}
|
||||
ts = &(sim->repl_state->trace);
|
||||
sim->core.trace_ctl = NULL; /* never leave a released ctl published */
|
||||
repl_trace_settings_unref(ts->cur);
|
||||
repl_trace_settings_unref(ts->next);
|
||||
ts->cur = NULL;
|
||||
ts->next = NULL;
|
||||
ts->session_in_progress = false;
|
||||
ts->round_traced = false;
|
||||
ts->round_flushed = false;
|
||||
repl_trace_store_fini(ts); /* frees the stored traces, zeroes ts */
|
||||
}
|
||||
|
||||
/* ---- `trace status` ------------------------------------------------------- */
|
||||
|
||||
static bool has_whole_array(const xce_sched_trace_ctl_t* ctl, uint64_t key)
|
||||
|
|
@ -494,6 +622,10 @@ bool repl_trace_cmd(xce_sched_sim_driver_t* sim, const char* raw_line)
|
|||
"trace off | trace status\n");
|
||||
} else if (word_is(w, "on")) {
|
||||
s = parse_on(p);
|
||||
if ((s != NULL) && (s->path != NULL) && !open_file(sim, s)) {
|
||||
repl_trace_settings_unref(s); /* 8.2, 8.3: nothing changes */
|
||||
s = NULL;
|
||||
}
|
||||
if (s != NULL) {
|
||||
repl_trace_settings_unref(ts->next);
|
||||
ts->next = s;
|
||||
|
|
|
|||
|
|
@ -2776,3 +2776,24 @@ sched_repl_trace_cmd_test = executable(
|
|||
|
||||
test('scheduler repl trace cmd test',
|
||||
sched_repl_trace_cmd_test, suite: 'scheduler')
|
||||
|
||||
# scheduler-sim-decision-trace task 4.5: the trace file copy and
|
||||
# output-file conflicts (design.md "ReplTraceControl" file rules, "Trace
|
||||
# settings lifecycle"; Requirements 8.2-8.7). Open at command time and open
|
||||
# failures, same-file rejection against the simulation output file and the
|
||||
# scheduler log (direct, hard link, symlink), the `set output` conflict
|
||||
# check, a replaced or disabled file kept open until the session ends or
|
||||
# closed at once, and shutdown flushing and closing every trace file. Same
|
||||
# source set as the trace cmd test; leak detection stays on.
|
||||
sched_repl_trace_file_test = executable(
|
||||
'sched_repl_trace_file_test',
|
||||
['test_repl_trace_file.c', '../src/sched_trace.c',
|
||||
'../src/sched_trace_scan.c', '../src/repl_trace_render.c',
|
||||
'../src/repl_trace_render_outcome.c', '../src/repl_fmt.c',
|
||||
'../src/repl_trace_store.c', '../src/repl_trace.c'],
|
||||
include_directories:[incdir],
|
||||
dependencies: [scheduler_test_deps],
|
||||
install: false)
|
||||
|
||||
test('scheduler repl trace file test',
|
||||
sched_repl_trace_file_test, suite: 'scheduler')
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@
|
|||
* A session in progress is simulated through the state the session hooks
|
||||
* (task 4.6) will maintain: `cur` takes a reference to `next` and
|
||||
* `session_in_progress` is set; the end drops `cur` and clears the flag.
|
||||
* Since task 4.5 a named trace file is opened at command time, so every
|
||||
* test runs in its own scratch directory with relative paths, and
|
||||
* tearDown releases everything through repl_trace_shutdown().
|
||||
*
|
||||
* Covers:
|
||||
* - every grammar form: `on all`, `on job SEL...`, `level`, `lines`,
|
||||
|
|
@ -29,6 +32,8 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ftw.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
|
@ -51,6 +56,8 @@
|
|||
|
||||
static xce_sched_sim_driver_t g_sim;
|
||||
static xce_sched_repl_state_t g_rs;
|
||||
static char g_dir[64]; /* per-test scratch directory */
|
||||
static char g_cwd[4096]; /* restored at tearDown */
|
||||
|
||||
/* ---- fixture job table (the store's no-trace replies; unused here) ------ */
|
||||
|
||||
|
|
@ -73,16 +80,48 @@ void setUp(void)
|
|||
memset(&g_rs, 0, sizeof(g_rs));
|
||||
g_rs.units_mode = XCE_REPL_UNITS_BOTH;
|
||||
g_sim.repl_state = &g_rs;
|
||||
|
||||
/* Task 4.5 opens a named trace file at command time, so every test runs
|
||||
* in its own scratch directory and uses relative paths. */
|
||||
TEST_ASSERT_NOT_NULL(getcwd(g_cwd, sizeof(g_cwd)));
|
||||
snprintf(g_dir, sizeof(g_dir), "/tmp/xce_trace_cmd_XXXXXX");
|
||||
TEST_ASSERT_NOT_NULL(mkdtemp(g_dir));
|
||||
TEST_ASSERT_EQUAL_INT(0, chdir(g_dir));
|
||||
TEST_ASSERT_EQUAL_INT(0, mkdir("a dir", 0700));
|
||||
}
|
||||
|
||||
static int rm_entry(const char* path, const struct stat* st, int flag,
|
||||
struct FTW* ftw)
|
||||
{
|
||||
(void)st;
|
||||
(void)flag;
|
||||
(void)ftw;
|
||||
return remove(path);
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
repl_trace_settings_unref(g_rs.trace.next);
|
||||
repl_trace_settings_unref(g_rs.trace.cur);
|
||||
g_rs.trace.next = NULL;
|
||||
g_rs.trace.cur = NULL;
|
||||
repl_trace_store_fini(&g_rs.trace);
|
||||
repl_trace_shutdown(&g_sim); /* releases next/cur and the store */
|
||||
TEST_ASSERT_NULL(g_rs.trace.next);
|
||||
TEST_ASSERT_NULL(g_rs.trace.cur);
|
||||
g_sim.repl_state = NULL;
|
||||
TEST_ASSERT_EQUAL_INT(0, chdir(g_cwd));
|
||||
nftw(g_dir, rm_entry, 16, FTW_DEPTH | FTW_PHYS);
|
||||
}
|
||||
|
||||
/* A path of exactly `len` bytes naming a file in the scratch directory:
|
||||
* "./" repeated, then a 5- or 6-character file name. */
|
||||
static void long_path(char* buf, size_t len)
|
||||
{
|
||||
const char* name = ((len - 5) % 2 == 0) ? "p.log" : "pp.log";
|
||||
size_t n = strlen(name);
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i + n < len; i += 2) {
|
||||
buf[i] = '.';
|
||||
buf[i + 1] = '/';
|
||||
}
|
||||
memcpy(buf + i, name, n + 1);
|
||||
}
|
||||
|
||||
/* ---- helpers ------------------------------------------------------------- */
|
||||
|
|
@ -149,15 +188,20 @@ static void assert_run(const char* line, const char* want)
|
|||
free(got);
|
||||
}
|
||||
|
||||
/* Runs a line that must succeed (prints no error) and returns `next`. */
|
||||
/* Runs a line that must succeed (prints only the enabled/disabled
|
||||
* confirmation) and returns `next`. */
|
||||
static xce_repl_trace_settings_t* run_ok(const char* line)
|
||||
{
|
||||
char* got = run(line);
|
||||
bool ok = (strcmp(got, "[sim] trace: enabled; applies from the next "
|
||||
"session\n") == 0) ||
|
||||
(strcmp(got, "[sim] trace: disabled; applies from the next "
|
||||
"session\n") == 0);
|
||||
|
||||
if (strstr(got, "invalid") != NULL) {
|
||||
fprintf(stderr, "unexpected error for '%s': %s", line, got);
|
||||
if (!ok) {
|
||||
fprintf(stderr, "unexpected output for '%s': %s", line, got);
|
||||
}
|
||||
TEST_ASSERT_NULL(strstr(got, "invalid"));
|
||||
TEST_ASSERT_TRUE(ok);
|
||||
free(got);
|
||||
return g_rs.trace.next;
|
||||
}
|
||||
|
|
@ -223,12 +267,12 @@ static void test_on_all_options(void)
|
|||
{
|
||||
const xce_sched_trace_ctl_t* ctl;
|
||||
|
||||
run_ok(" trace\ton all level full lines 50 file /tmp/t.log ");
|
||||
run_ok(" trace\ton all level full lines 50 file t.log ");
|
||||
ctl = next_ctl();
|
||||
TEST_ASSERT_TRUE(ctl->all);
|
||||
TEST_ASSERT_EQUAL_INT(XCE_TRACE_LEVEL_FULL, ctl->level);
|
||||
TEST_ASSERT_EQUAL_INT(50, ctl->line_bound);
|
||||
TEST_ASSERT_EQUAL_STRING("/tmp/t.log", next_path());
|
||||
TEST_ASSERT_EQUAL_STRING("t.log", next_path());
|
||||
|
||||
run_ok("trace on all lines 100000 level summary");
|
||||
ctl = next_ctl();
|
||||
|
|
@ -312,25 +356,23 @@ static void test_file_paths(void)
|
|||
char path[4097];
|
||||
char* got;
|
||||
|
||||
run_ok("trace on all file \"/tmp/a dir/t.log\" ");
|
||||
TEST_ASSERT_EQUAL_STRING("/tmp/a dir/t.log", next_path());
|
||||
run_ok("trace on all file '/tmp/q.log'");
|
||||
TEST_ASSERT_EQUAL_STRING("/tmp/q.log", next_path());
|
||||
run_ok("trace on all level full file /tmp/x y.log");
|
||||
TEST_ASSERT_EQUAL_STRING("/tmp/x y.log", next_path());
|
||||
run_ok("trace on all file \"a dir/t.log\" ");
|
||||
TEST_ASSERT_EQUAL_STRING("a dir/t.log", next_path());
|
||||
run_ok("trace on all file 'q.log'");
|
||||
TEST_ASSERT_EQUAL_STRING("q.log", next_path());
|
||||
run_ok("trace on all level full file x y.log");
|
||||
TEST_ASSERT_EQUAL_STRING("x y.log", next_path());
|
||||
|
||||
/* 200 characters */
|
||||
memset(path, 'p', 200);
|
||||
path[0] = '/';
|
||||
path[200] = '\0';
|
||||
long_path(path, 200);
|
||||
TEST_ASSERT_EQUAL_INT(200, (int)strlen(path));
|
||||
snprintf(line, sizeof(line), "trace on all file %s", path);
|
||||
run_ok(line);
|
||||
TEST_ASSERT_EQUAL_STRING(path, next_path());
|
||||
|
||||
/* 4095 bytes accepted, quoted */
|
||||
memset(path, 'p', 4095);
|
||||
path[0] = '/';
|
||||
path[4095] = '\0';
|
||||
long_path(path, 4095);
|
||||
TEST_ASSERT_EQUAL_INT(4095, (int)strlen(path));
|
||||
snprintf(line, sizeof(line), "trace on all file \"%s\"", path);
|
||||
run_ok(line);
|
||||
TEST_ASSERT_EQUAL_STRING(path, next_path());
|
||||
|
|
@ -376,7 +418,7 @@ static void test_invalid_arguments_leave_settings(void)
|
|||
char line[256];
|
||||
char want[512];
|
||||
|
||||
run_ok("trace on all level full lines 70 file /tmp/keep.log");
|
||||
run_ok("trace on all level full lines 70 file keep.log");
|
||||
|
||||
assert_rejected("trace", "[sim] trace: usage: trace on all|job <jobid> "
|
||||
"... [level summary|full] [lines N] "
|
||||
|
|
@ -426,7 +468,7 @@ static void test_invalid_arguments_leave_settings(void)
|
|||
TEST_ASSERT_TRUE(next_ctl()->all);
|
||||
TEST_ASSERT_EQUAL_INT(XCE_TRACE_LEVEL_FULL, next_ctl()->level);
|
||||
TEST_ASSERT_EQUAL_INT(70, next_ctl()->line_bound);
|
||||
TEST_ASSERT_EQUAL_STRING("/tmp/keep.log", next_path());
|
||||
TEST_ASSERT_EQUAL_STRING("keep.log", next_path());
|
||||
}
|
||||
|
||||
/* ---- settings lifecycle and status (1.4, 1.6, 1.7, 1.10) ---------------- */
|
||||
|
|
@ -449,7 +491,7 @@ static void test_status_next_settings(void)
|
|||
char line[256];
|
||||
|
||||
snprintf(line, sizeof(line),
|
||||
"trace on job %s %s[1-3] %s level full lines 300 file /tmp/s.log",
|
||||
"trace on job %s %s[1-3] %s level full lines 300 file s.log",
|
||||
fmt_id(PLAIN_JOB), fmt_id(ARRAY_BASE), fmt_id(PLAIN_JOB2));
|
||||
run_ok(line);
|
||||
snprintf(want, sizeof(want),
|
||||
|
|
@ -458,7 +500,7 @@ static void test_status_next_settings(void)
|
|||
" selection: jobs %s,%s[1-3],%s\n"
|
||||
" level: full\n"
|
||||
" line bound: 300\n"
|
||||
" file: /tmp/s.log\n"
|
||||
" file: s.log\n"
|
||||
"%s",
|
||||
fmt_id(PLAIN_JOB), fmt_id(ARRAY_BASE), fmt_id(PLAIN_JOB2),
|
||||
store_line());
|
||||
|
|
@ -534,7 +576,7 @@ static void test_status_before_during_after_session(void)
|
|||
assert_run("trace status", want);
|
||||
|
||||
/* during, replaced: cur is untouched, the replacement is shown */
|
||||
snprintf(line, sizeof(line), "trace on job %s level full file /tmp/n.log",
|
||||
snprintf(line, sizeof(line), "trace on job %s level full file n.log",
|
||||
fmt_id(PLAIN_JOB));
|
||||
run_ok(line);
|
||||
TEST_ASSERT_TRUE(started == g_rs.trace.cur);
|
||||
|
|
@ -552,7 +594,7 @@ static void test_status_before_during_after_session(void)
|
|||
" selection: jobs %s\n"
|
||||
" level: full\n"
|
||||
" line bound: 1000\n"
|
||||
" file: /tmp/n.log\n"
|
||||
" file: n.log\n"
|
||||
"%s",
|
||||
fmt_id(PLAIN_JOB), store_line());
|
||||
assert_run("trace status", want);
|
||||
|
|
@ -620,12 +662,12 @@ static void test_settings_refcount(void)
|
|||
TEST_ASSERT_NULL(repl_trace_settings_ref(NULL));
|
||||
repl_trace_settings_unref(NULL);
|
||||
|
||||
s = run_ok("trace on all lines 123 file /tmp/r.log");
|
||||
s = run_ok("trace on all lines 123 file r.log");
|
||||
TEST_ASSERT_TRUE(repl_trace_settings_ref(s) == s);
|
||||
run_ok("trace off");
|
||||
/* still alive through the extra reference (ASAN would flag a free) */
|
||||
TEST_ASSERT_EQUAL_INT(123, repl_trace_settings_ctl(s)->line_bound);
|
||||
TEST_ASSERT_EQUAL_STRING("/tmp/r.log", repl_trace_settings_path(s));
|
||||
TEST_ASSERT_EQUAL_STRING("r.log", repl_trace_settings_path(s));
|
||||
repl_trace_settings_unref(s);
|
||||
}
|
||||
|
||||
|
|
|
|||
531
src/scheduler/testsuite/test_repl_trace_file.c
Normal file
531
src/scheduler/testsuite/test_repl_trace_file.c
Normal file
|
|
@ -0,0 +1,531 @@
|
|||
/*
|
||||
* test_repl_trace_file.c - the trace file copy and output-file conflicts
|
||||
* (scheduler-sim-decision-trace spec, task 4.5; design.md
|
||||
* "ReplTraceControl" file rules, "Trace settings lifecycle", "Error
|
||||
* Handling"; Requirements 8.2-8.7).
|
||||
*
|
||||
* Links the trace control (src/repl_trace.c), the store, the record model,
|
||||
* the renderer and the shared REPL formatting module, like
|
||||
* test_repl_trace_cmd.c. Every test runs in its own scratch directory.
|
||||
* A session in progress is simulated through the state the session hooks
|
||||
* (task 4.6) maintain: `cur` takes a reference to `next` and
|
||||
* `session_in_progress` is set; the end drops `cur` and clears the flag.
|
||||
*
|
||||
* Covers:
|
||||
* - a named file is opened for appending at command time; an open
|
||||
* failure names the file and the cause and leaves `next` unchanged
|
||||
* (8.2);
|
||||
* - a path that is the simulation output file or the scheduler log,
|
||||
* directly, through a hard link or through a symlink, is rejected and
|
||||
* leaves `next` unchanged (8.3);
|
||||
* - repl_trace_file_conflicts() matches the files of `next` and `cur`
|
||||
* by device and inode, for `set output` (8.4);
|
||||
* - a replaced or disabled file stays open until the session in progress
|
||||
* ends, and is closed at once when no session is in progress (8.5);
|
||||
* - repl_trace_shutdown() flushes and closes every open trace file and
|
||||
* releases the settings and the store (8.6);
|
||||
* - nothing is written to the scheduler log (8.7).
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <ftw.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "repl_internal.h"
|
||||
#include "sched/trace.h"
|
||||
#include "sim.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define ENABLED "[sim] trace: enabled; applies from the next session\n"
|
||||
#define DISABLED "[sim] trace: disabled; applies from the next session\n"
|
||||
|
||||
static xce_sched_sim_driver_t g_sim;
|
||||
static xce_sched_repl_state_t g_rs;
|
||||
static char g_dir[64];
|
||||
static char g_cwd[4096];
|
||||
|
||||
/* ---- fixture job table (the store's no-trace replies; unused here) ------ */
|
||||
|
||||
xce_sched_job_t* sched_get_job(xce_sched_core_t* core, uint64_t jobid)
|
||||
{
|
||||
(void)core;
|
||||
(void)jobid;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* repl_job_state_name(uint32_t state)
|
||||
{
|
||||
(void)state;
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
static int rm_entry(const char* path, const struct stat* st, int flag,
|
||||
struct FTW* ftw)
|
||||
{
|
||||
(void)st;
|
||||
(void)flag;
|
||||
(void)ftw;
|
||||
return remove(path);
|
||||
}
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
memset(&g_sim, 0, sizeof(g_sim));
|
||||
memset(&g_rs, 0, sizeof(g_rs));
|
||||
g_rs.units_mode = XCE_REPL_UNITS_BOTH;
|
||||
g_sim.repl_state = &g_rs;
|
||||
|
||||
TEST_ASSERT_NOT_NULL(getcwd(g_cwd, sizeof(g_cwd)));
|
||||
snprintf(g_dir, sizeof(g_dir), "/tmp/xce_trace_file_XXXXXX");
|
||||
TEST_ASSERT_NOT_NULL(mkdtemp(g_dir));
|
||||
TEST_ASSERT_EQUAL_INT(0, chdir(g_dir));
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
repl_trace_shutdown(&g_sim);
|
||||
if (g_sim.record_file != NULL) {
|
||||
fclose(g_sim.record_file);
|
||||
g_sim.record_file = NULL;
|
||||
}
|
||||
free(g_sim.log_path);
|
||||
g_sim.log_path = NULL;
|
||||
g_sim.repl_state = NULL;
|
||||
TEST_ASSERT_EQUAL_INT(0, chdir(g_cwd));
|
||||
nftw(g_dir, rm_entry, 16, FTW_DEPTH | FTW_PHYS);
|
||||
}
|
||||
|
||||
/* ---- helpers ------------------------------------------------------------- */
|
||||
|
||||
/* Runs one raw `trace ...` line with stdout redirected to a temp file and
|
||||
* returns what it printed (caller frees). */
|
||||
static char* run(const char* line)
|
||||
{
|
||||
char tmp[] = "/tmp/xce_trace_file_stdout_XXXXXX";
|
||||
int fd = mkstemp(tmp);
|
||||
int saved;
|
||||
off_t len;
|
||||
char* buf;
|
||||
|
||||
TEST_ASSERT_TRUE(fd >= 0);
|
||||
fflush(stdout);
|
||||
saved = dup(STDOUT_FILENO);
|
||||
TEST_ASSERT_TRUE(saved >= 0);
|
||||
TEST_ASSERT_TRUE(dup2(fd, STDOUT_FILENO) >= 0);
|
||||
|
||||
TEST_ASSERT_FALSE(repl_trace_cmd(&g_sim, line));
|
||||
|
||||
fflush(stdout);
|
||||
dup2(saved, STDOUT_FILENO);
|
||||
close(saved);
|
||||
|
||||
len = lseek(fd, 0, SEEK_CUR);
|
||||
TEST_ASSERT_TRUE(len >= 0);
|
||||
buf = (char*)calloc(1, (size_t)len + 1);
|
||||
TEST_ASSERT_NOT_NULL(buf);
|
||||
TEST_ASSERT_TRUE(lseek(fd, 0, SEEK_SET) == 0);
|
||||
if (len > 0) {
|
||||
TEST_ASSERT_TRUE(read(fd, buf, (size_t)len) == len);
|
||||
}
|
||||
close(fd);
|
||||
unlink(tmp);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void assert_run(const char* line, const char* want)
|
||||
{
|
||||
char* got = run(line);
|
||||
|
||||
if (strcmp(want, got) != 0) {
|
||||
fprintf(stderr, "---- want ----\n%s---- got ----\n%s----\n", want,
|
||||
got);
|
||||
}
|
||||
TEST_ASSERT_EQUAL_STRING(want, got);
|
||||
free(got);
|
||||
}
|
||||
|
||||
/* How many of this process's descriptors are open on `path` (by device
|
||||
* and inode, so a hard link counts as the same file). */
|
||||
static int open_count(const char* path)
|
||||
{
|
||||
struct stat want;
|
||||
struct stat st;
|
||||
DIR* d;
|
||||
struct dirent* e;
|
||||
char fdpath[PATH_MAX];
|
||||
int n = 0;
|
||||
|
||||
if (stat(path, &want) != 0) {
|
||||
return 0;
|
||||
}
|
||||
d = opendir("/proc/self/fd");
|
||||
TEST_ASSERT_NOT_NULL(d);
|
||||
while ((e = readdir(d)) != NULL) {
|
||||
if (e->d_name[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
snprintf(fdpath, sizeof(fdpath), "/proc/self/fd/%s", e->d_name);
|
||||
if ((stat(fdpath, &st) == 0) && (st.st_dev == want.st_dev) &&
|
||||
(st.st_ino == want.st_ino)) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
return n;
|
||||
}
|
||||
|
||||
static void touch(const char* path)
|
||||
{
|
||||
FILE* f = fopen(path, "a");
|
||||
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
static long file_size(const char* path)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, stat(path, &st));
|
||||
return (long)st.st_size;
|
||||
}
|
||||
|
||||
static char* slurp(const char* path)
|
||||
{
|
||||
long n = file_size(path);
|
||||
char* b = (char*)calloc(1, (size_t)n + 1);
|
||||
FILE* f = fopen(path, "r");
|
||||
|
||||
TEST_ASSERT_NOT_NULL(b);
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
TEST_ASSERT_EQUAL_INT((int)n, (int)fread(b, 1, (size_t)n, f));
|
||||
fclose(f);
|
||||
return b;
|
||||
}
|
||||
|
||||
/* The simulated session hooks (task 4.6 owns the real ones). */
|
||||
static void session_begin(void)
|
||||
{
|
||||
g_rs.trace.cur = repl_trace_settings_ref(g_rs.trace.next);
|
||||
g_rs.trace.session_in_progress = true;
|
||||
}
|
||||
|
||||
static void session_end(void)
|
||||
{
|
||||
repl_trace_settings_unref(g_rs.trace.cur);
|
||||
g_rs.trace.cur = NULL;
|
||||
g_rs.trace.session_in_progress = false;
|
||||
}
|
||||
|
||||
/* An --sim-output / `set output` file in effect. */
|
||||
static void set_output(const char* path)
|
||||
{
|
||||
g_sim.record_file = fopen(path, "w");
|
||||
TEST_ASSERT_NOT_NULL(g_sim.record_file);
|
||||
}
|
||||
|
||||
/* ---- 8.2: open at command time, open failure ----------------------------- */
|
||||
|
||||
static void test_file_opened_for_append_at_command_time(void)
|
||||
{
|
||||
FILE* f = fopen("t.log", "w");
|
||||
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
fputs("earlier\n", f);
|
||||
fclose(f);
|
||||
|
||||
assert_run("trace on all file t.log", ENABLED);
|
||||
TEST_ASSERT_NOT_NULL(g_rs.trace.next);
|
||||
TEST_ASSERT_NOT_NULL(repl_trace_settings_file(g_rs.trace.next));
|
||||
TEST_ASSERT_EQUAL_INT(1, open_count("t.log"));
|
||||
|
||||
/* appended, never truncated */
|
||||
fputs("traced\n", repl_trace_settings_file(g_rs.trace.next));
|
||||
fflush(repl_trace_settings_file(g_rs.trace.next));
|
||||
{
|
||||
char* got = slurp("t.log");
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING("earlier\ntraced\n", got);
|
||||
free(got);
|
||||
}
|
||||
|
||||
/* a new file is created at once */
|
||||
assert_run("trace on all file new.log", ENABLED);
|
||||
TEST_ASSERT_EQUAL_INT(0, access("new.log", F_OK));
|
||||
TEST_ASSERT_EQUAL_INT(1, open_count("new.log"));
|
||||
|
||||
/* no file named: none opened */
|
||||
assert_run("trace on all", ENABLED);
|
||||
TEST_ASSERT_NULL(repl_trace_settings_file(g_rs.trace.next));
|
||||
}
|
||||
|
||||
static void test_open_failure_leaves_settings_unchanged(void)
|
||||
{
|
||||
xce_repl_trace_settings_t* before;
|
||||
char want[256];
|
||||
|
||||
assert_run("trace on all lines 77 file keep.log", ENABLED);
|
||||
before = g_rs.trace.next;
|
||||
|
||||
snprintf(want, sizeof(want),
|
||||
"[sim] trace: cannot open file no such dir/t.log: %s\n",
|
||||
strerror(ENOENT));
|
||||
assert_run("trace on all level full file \"no such dir/t.log\"", want);
|
||||
TEST_ASSERT_TRUE(g_rs.trace.next == before);
|
||||
TEST_ASSERT_EQUAL_INT(77, repl_trace_settings_ctl(before)->line_bound);
|
||||
TEST_ASSERT_EQUAL_STRING("keep.log", repl_trace_settings_path(before));
|
||||
TEST_ASSERT_EQUAL_INT(1, open_count("keep.log"));
|
||||
|
||||
/* a directory cannot be opened for writing either */
|
||||
TEST_ASSERT_EQUAL_INT(0, mkdir("adir", 0700));
|
||||
snprintf(want, sizeof(want), "[sim] trace: cannot open file adir: %s\n",
|
||||
strerror(EISDIR));
|
||||
assert_run("trace on job 1-2-1000-42 file adir", want);
|
||||
TEST_ASSERT_TRUE(g_rs.trace.next == before);
|
||||
|
||||
/* also with tracing off: it stays off */
|
||||
assert_run("trace off", DISABLED);
|
||||
assert_run("trace on all file \"no such dir/t.log\"",
|
||||
"[sim] trace: cannot open file no such dir/t.log: "
|
||||
"No such file or directory\n");
|
||||
TEST_ASSERT_NULL(g_rs.trace.next);
|
||||
}
|
||||
|
||||
/* ---- 8.3: same file as the simulation output or the scheduler log ------- */
|
||||
|
||||
static void test_rejects_sim_output_file(void)
|
||||
{
|
||||
xce_repl_trace_settings_t* before;
|
||||
const char* want = "[sim] trace: file out.replay is the simulation "
|
||||
"output file; choose another file\n";
|
||||
|
||||
set_output("out.replay");
|
||||
assert_run("trace on all file keep.log", ENABLED);
|
||||
before = g_rs.trace.next;
|
||||
|
||||
assert_run("trace on all file out.replay", want);
|
||||
TEST_ASSERT_TRUE(g_rs.trace.next == before);
|
||||
TEST_ASSERT_EQUAL_INT(1, open_count("out.replay")); /* only record_file */
|
||||
|
||||
/* through a hard link and a symlink */
|
||||
TEST_ASSERT_EQUAL_INT(0, link("out.replay", "hard.replay"));
|
||||
assert_run("trace on all file hard.replay",
|
||||
"[sim] trace: file hard.replay is the simulation output file; "
|
||||
"choose another file\n");
|
||||
TEST_ASSERT_EQUAL_INT(0, symlink("out.replay", "soft.replay"));
|
||||
assert_run("trace on all file soft.replay",
|
||||
"[sim] trace: file soft.replay is the simulation output file; "
|
||||
"choose another file\n");
|
||||
TEST_ASSERT_TRUE(g_rs.trace.next == before);
|
||||
TEST_ASSERT_EQUAL_INT(0, (int)file_size("out.replay"));
|
||||
|
||||
/* with no output file in effect, the same path is accepted */
|
||||
fclose(g_sim.record_file);
|
||||
g_sim.record_file = NULL;
|
||||
assert_run("trace on all file out.replay", ENABLED);
|
||||
}
|
||||
|
||||
static void test_rejects_scheduler_log(void)
|
||||
{
|
||||
xce_repl_trace_settings_t* before;
|
||||
|
||||
touch("sched.log");
|
||||
g_sim.log_path = strdup("sched.log");
|
||||
TEST_ASSERT_NOT_NULL(g_sim.log_path);
|
||||
assert_run("trace on all file keep.log", ENABLED);
|
||||
before = g_rs.trace.next;
|
||||
|
||||
assert_run("trace on all file sched.log",
|
||||
"[sim] trace: file sched.log is the scheduler log file; "
|
||||
"choose another file\n");
|
||||
TEST_ASSERT_TRUE(g_rs.trace.next == before);
|
||||
TEST_ASSERT_EQUAL_INT(0, open_count("sched.log"));
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, link("sched.log", "hard.log"));
|
||||
assert_run("trace on job 1-2-1000-43 file hard.log",
|
||||
"[sim] trace: file hard.log is the scheduler log file; "
|
||||
"choose another file\n");
|
||||
TEST_ASSERT_TRUE(g_rs.trace.next == before);
|
||||
TEST_ASSERT_EQUAL_INT(0, open_count("sched.log"));
|
||||
|
||||
/* 8.7: tracing never writes to the scheduler log */
|
||||
assert_run("trace off", DISABLED);
|
||||
TEST_ASSERT_EQUAL_INT(0, (int)file_size("sched.log"));
|
||||
|
||||
/* a log path that does not exist yet conflicts with nothing */
|
||||
free(g_sim.log_path);
|
||||
g_sim.log_path = strdup("missing.log");
|
||||
assert_run("trace on all file other.log", ENABLED);
|
||||
}
|
||||
|
||||
/* ---- 8.4: the conflict check for `set output` ----------------------------- */
|
||||
|
||||
static void test_conflict_check(void)
|
||||
{
|
||||
TEST_ASSERT_FALSE(repl_trace_file_conflicts(&g_sim, "a.log"));
|
||||
TEST_ASSERT_FALSE(repl_trace_file_conflicts(&g_sim, NULL));
|
||||
|
||||
assert_run("trace on all file a.log", ENABLED);
|
||||
session_begin();
|
||||
assert_run("trace on all file b.log", ENABLED); /* a.log kept by cur */
|
||||
|
||||
TEST_ASSERT_TRUE(repl_trace_file_conflicts(&g_sim, "a.log"));
|
||||
TEST_ASSERT_TRUE(repl_trace_file_conflicts(&g_sim, "b.log"));
|
||||
TEST_ASSERT_TRUE(repl_trace_file_conflicts(&g_sim, "./b.log"));
|
||||
TEST_ASSERT_EQUAL_INT(0, link("a.log", "a_hard.log"));
|
||||
TEST_ASSERT_TRUE(repl_trace_file_conflicts(&g_sim, "a_hard.log"));
|
||||
TEST_ASSERT_FALSE(repl_trace_file_conflicts(&g_sim, "c.log"));
|
||||
touch("c.log");
|
||||
TEST_ASSERT_FALSE(repl_trace_file_conflicts(&g_sim, "c.log"));
|
||||
|
||||
/* once the session ends, a.log is closed and no longer conflicts */
|
||||
session_end();
|
||||
TEST_ASSERT_FALSE(repl_trace_file_conflicts(&g_sim, "a.log"));
|
||||
TEST_ASSERT_TRUE(repl_trace_file_conflicts(&g_sim, "b.log"));
|
||||
|
||||
assert_run("trace off", DISABLED);
|
||||
TEST_ASSERT_FALSE(repl_trace_file_conflicts(&g_sim, "b.log"));
|
||||
}
|
||||
|
||||
/* ---- 8.5: replaced and disabled files ------------------------------------ */
|
||||
|
||||
static void test_replaced_file_kept_open_until_session_ends(void)
|
||||
{
|
||||
FILE* old;
|
||||
|
||||
assert_run("trace on all file a.log", ENABLED);
|
||||
session_begin();
|
||||
old = repl_trace_settings_file(g_rs.trace.cur);
|
||||
TEST_ASSERT_NOT_NULL(old);
|
||||
|
||||
assert_run("trace on job 1-2-1000-44 file b.log", ENABLED);
|
||||
TEST_ASSERT_EQUAL_INT(1, open_count("a.log"));
|
||||
TEST_ASSERT_EQUAL_INT(1, open_count("b.log"));
|
||||
TEST_ASSERT_TRUE(repl_trace_settings_file(g_rs.trace.cur) == old);
|
||||
/* traces captured under the old settings still go to the old file */
|
||||
fputs("late trace\n", old);
|
||||
|
||||
session_end();
|
||||
TEST_ASSERT_EQUAL_INT(0, open_count("a.log"));
|
||||
TEST_ASSERT_EQUAL_INT(1, open_count("b.log"));
|
||||
{
|
||||
char* got = slurp("a.log");
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING("late trace\n", got); /* flushed by close */
|
||||
free(got);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_disabled_file_kept_open_until_session_ends(void)
|
||||
{
|
||||
assert_run("trace on all file a.log", ENABLED);
|
||||
session_begin();
|
||||
assert_run("trace off", DISABLED);
|
||||
TEST_ASSERT_EQUAL_INT(1, open_count("a.log"));
|
||||
session_end();
|
||||
TEST_ASSERT_EQUAL_INT(0, open_count("a.log"));
|
||||
}
|
||||
|
||||
static void test_immediate_close_without_session(void)
|
||||
{
|
||||
assert_run("trace on all file a.log", ENABLED);
|
||||
TEST_ASSERT_EQUAL_INT(1, open_count("a.log"));
|
||||
assert_run("trace on all file b.log", ENABLED);
|
||||
TEST_ASSERT_EQUAL_INT(0, open_count("a.log"));
|
||||
TEST_ASSERT_EQUAL_INT(1, open_count("b.log"));
|
||||
assert_run("trace off", DISABLED);
|
||||
TEST_ASSERT_EQUAL_INT(0, open_count("b.log"));
|
||||
|
||||
/* a session that ended leaves nothing held either */
|
||||
assert_run("trace on all file c.log", ENABLED);
|
||||
session_begin();
|
||||
session_end();
|
||||
TEST_ASSERT_EQUAL_INT(1, open_count("c.log"));
|
||||
assert_run("trace off", DISABLED);
|
||||
TEST_ASSERT_EQUAL_INT(0, open_count("c.log"));
|
||||
}
|
||||
|
||||
/* ---- 8.6: shutdown -------------------------------------------------------- */
|
||||
|
||||
static void test_shutdown_flushes_and_closes_every_file(void)
|
||||
{
|
||||
assert_run("trace on all file a.log", ENABLED);
|
||||
session_begin();
|
||||
assert_run("trace on all file b.log", ENABLED);
|
||||
g_sim.core.trace_ctl = repl_trace_settings_ctl(g_rs.trace.cur);
|
||||
|
||||
/* buffered, not yet flushed */
|
||||
fputs("trace in a\n", repl_trace_settings_file(g_rs.trace.cur));
|
||||
fputs("trace in b\n", repl_trace_settings_file(g_rs.trace.next));
|
||||
TEST_ASSERT_EQUAL_INT(0, (int)file_size("a.log"));
|
||||
TEST_ASSERT_EQUAL_INT(0, (int)file_size("b.log"));
|
||||
|
||||
repl_trace_shutdown(&g_sim);
|
||||
|
||||
TEST_ASSERT_NULL(g_rs.trace.next);
|
||||
TEST_ASSERT_NULL(g_rs.trace.cur);
|
||||
TEST_ASSERT_FALSE(g_rs.trace.session_in_progress);
|
||||
TEST_ASSERT_NULL(g_sim.core.trace_ctl); /* nothing left published */
|
||||
TEST_ASSERT_EQUAL_INT(0, open_count("a.log"));
|
||||
TEST_ASSERT_EQUAL_INT(0, open_count("b.log"));
|
||||
{
|
||||
char* a = slurp("a.log");
|
||||
char* b = slurp("b.log");
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING("trace in a\n", a);
|
||||
TEST_ASSERT_EQUAL_STRING("trace in b\n", b);
|
||||
free(a);
|
||||
free(b);
|
||||
}
|
||||
|
||||
/* idempotent, and safe with nothing set */
|
||||
repl_trace_shutdown(&g_sim);
|
||||
g_sim.repl_state = NULL;
|
||||
repl_trace_shutdown(&g_sim);
|
||||
repl_trace_shutdown(NULL);
|
||||
g_sim.repl_state = &g_rs;
|
||||
}
|
||||
|
||||
/* Shutdown also frees the stored traces (task 4.2's store) so nothing
|
||||
* leaks at REPL exit; leak detection is on for this test binary. */
|
||||
static void test_shutdown_releases_store(void)
|
||||
{
|
||||
xce_sched_trace_ctl_t ctl;
|
||||
xce_sched_trace_rec_t* rec;
|
||||
|
||||
memset(&ctl, 0, sizeof(ctl));
|
||||
ctl.all = true;
|
||||
ctl.level = XCE_TRACE_LEVEL_SUMMARY;
|
||||
ctl.line_bound = XCE_TRACE_LINE_BOUND_DEFAULT;
|
||||
rec = sched_trace_rec_new(&ctl, (uint64_t)42, XCE_TRACE_REC_DECISION);
|
||||
TEST_ASSERT_NOT_NULL(rec);
|
||||
repl_trace_store_put(&g_rs.trace, rec);
|
||||
TEST_ASSERT_TRUE(repl_trace_store_bytes(&g_rs.trace) > 0);
|
||||
|
||||
repl_trace_shutdown(&g_sim);
|
||||
TEST_ASSERT_EQUAL_INT(0, (int)repl_trace_store_bytes(&g_rs.trace));
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_file_opened_for_append_at_command_time);
|
||||
RUN_TEST(test_open_failure_leaves_settings_unchanged);
|
||||
RUN_TEST(test_rejects_sim_output_file);
|
||||
RUN_TEST(test_rejects_scheduler_log);
|
||||
RUN_TEST(test_conflict_check);
|
||||
RUN_TEST(test_replaced_file_kept_open_until_session_ends);
|
||||
RUN_TEST(test_disabled_file_kept_open_until_session_ends);
|
||||
RUN_TEST(test_immediate_close_without_session);
|
||||
RUN_TEST(test_shutdown_flushes_and_closes_every_file);
|
||||
RUN_TEST(test_shutdown_releases_store);
|
||||
return UNITY_END();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue