fix(pipeline): align parallel env access graphing

Reuse the existing sequential EnvVar/CONFIGURES materialization helper from the parallel full-index registry phase so full rebuilds remain a valid oracle for incremental slices.

Add a sequential-vs-parallel pipeline canary for C getenv access that verifies the CONFIGURES edge and canonical graph equality. This fixes the route_handler self-dogfood mismatch where incremental containment had EnvVar data that parallel full rebuild omitted.

Validation: make -f Makefile.cbm -j8 build/c/test-runner; CBM_ONLY_SUITE=pipeline build/c/test-runner; bash scripts/check-source-safety.sh src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pipeline_internal.h tests/test_pipeline.c; git diff --check; make -f Makefile.cbm -j8 cbm; route_handler self-dogfood rerun passed.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
This commit is contained in:
Andrew Hundt
2026-07-04 05:25:19 -04:00
parent e41a49d4b7
commit 0f12f28b78
4 changed files with 88 additions and 4 deletions
+4 -4
View File
@@ -371,8 +371,8 @@ static void create_channel_edges_for_file(cbm_pipeline_ctx_t *ctx, const CBMFile
* env key and link the enclosing function (or the file node) CONFIGURES-> it,
* so environment-driven configuration is visible even when the accessor is a
* stdlib symbol that never resolves to an in-graph callee. */
static int create_env_configures_for_file(cbm_pipeline_ctx_t *ctx, const CBMFileResult *result,
const char *rel) {
int cbm_pipeline_create_env_configures_for_file(cbm_pipeline_ctx_t *ctx,
const CBMFileResult *result, const char *rel) {
int count = 0;
char *file_qn = NULL;
const cbm_gbuf_node_t *file_node = NULL;
@@ -491,7 +491,7 @@ int cbm_pipeline_pass_definitions(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t
* map is available without the cache (single-file scope). */
total_imports += cbm_pipeline_create_import_edges_for_file(ctx, result, rel, NULL);
create_channel_edges_for_file(ctx, result, rel);
create_env_configures_for_file(ctx, result, rel);
cbm_pipeline_create_env_configures_for_file(ctx, result, rel);
cbm_free_result(result);
}
}
@@ -524,7 +524,7 @@ int cbm_pipeline_pass_definitions(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t
total_imports += cbm_pipeline_create_import_edges_for_file(
ctx, result, files[i].rel_path, namespace_map);
create_channel_edges_for_file(ctx, result, files[i].rel_path);
create_env_configures_for_file(ctx, result, files[i].rel_path);
cbm_pipeline_create_env_configures_for_file(ctx, result, files[i].rel_path);
}
cbm_pipeline_namespace_map_free(namespace_map);
if (owns_local_cache) {
+1
View File
@@ -889,6 +889,7 @@ int cbm_build_registry_from_cache(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t
imports_edges += cbm_pipeline_create_import_edges_for_file(ctx, result, rel, namespace_map);
create_channel_edges(ctx, result, rel);
cbm_pipeline_create_env_configures_for_file(ctx, result, rel);
}
cbm_pipeline_namespace_map_free(namespace_map);
+3
View File
@@ -395,6 +395,9 @@ int cbm_pipeline_create_import_edges_for_file(cbm_pipeline_ctx_t *ctx,
const CBMFileResult *result,
const char *rel_path,
CBMHashTable *namespace_map);
int cbm_pipeline_create_env_configures_for_file(cbm_pipeline_ctx_t *ctx,
const CBMFileResult *result,
const char *rel_path);
/* Extract IMPORTS edge local_name from the canonical edge JSON. Caller frees. */
char *cbm_pipeline_import_edge_local_name_dup(const cbm_gbuf_edge_t *edge);
+80
View File
@@ -2485,6 +2485,85 @@ TEST(pipeline_parallel_duplicate_import_inherits_matches_sequential) {
PASS();
}
TEST(pipeline_parallel_env_access_matches_sequential) {
enum {
FILLER_FILE_COUNT = 52,
FILE_COUNT = FILLER_FILE_COUNT + 1,
SEQUENTIAL_WORKERS = 1,
PARALLEL_WORKERS = 4
};
const char *files[FILE_COUNT];
const char *contents[FILE_COUNT];
char filler_files[FILLER_FILE_COUNT][CBM_SZ_64];
char filler_bodies[FILLER_FILE_COUNT][CBM_SZ_128];
files[0] = "src/env.c";
contents[0] = "#include <stdlib.h>\n\n"
"const char *load_temp(void) {\n"
" return getenv(\"CBM_TEST_PARALLEL_ENV\");\n"
"}\n";
for (int i = 0; i < FILLER_FILE_COUNT; i++) {
int rn = snprintf(filler_files[i], sizeof(filler_files[i]), "fillers/filler_%02d.c", i);
int bn = snprintf(filler_bodies[i], sizeof(filler_bodies[i]),
"int filler_%02d(void) {\n return %d;\n}\n", i, i);
ASSERT_GT(rn, 0);
ASSERT_LT((size_t)rn, sizeof(filler_files[i]));
ASSERT_GT(bn, 0);
ASSERT_LT((size_t)bn, sizeof(filler_bodies[i]));
files[i + 1] = filler_files[i];
contents[i + 1] = filler_bodies[i];
}
if (setup_lang_repo(files, contents, FILE_COUNT) != 0) {
FAIL("tmpdir");
}
char seq_db[CBM_SZ_512];
char par_db[CBM_SZ_512];
int n = snprintf(seq_db, sizeof(seq_db), "%s/env-seq.db", g_lang_tmpdir);
ASSERT_GT(n, 0);
ASSERT_LT((size_t)n, sizeof(seq_db));
n = snprintf(par_db, sizeof(par_db), "%s/env-par.db", g_lang_tmpdir);
ASSERT_GT(n, 0);
ASSERT_LT((size_t)n, sizeof(par_db));
char saved_workers[CBM_SZ_32] = {0};
bool had_workers = cbm_safe_getenv("CBM_WORKERS", saved_workers, sizeof(saved_workers),
NULL) != NULL;
char *project = NULL;
int seq_rc =
pipeline_run_with_worker_count(g_lang_tmpdir, seq_db, SEQUENTIAL_WORKERS, &project);
int par_rc = pipeline_run_with_worker_count(g_lang_tmpdir, par_db, PARALLEL_WORKERS, NULL);
pipeline_restore_workers_env(had_workers, saved_workers);
ASSERT_EQ(seq_rc, 0);
ASSERT_EQ(par_rc, 0);
ASSERT_NOT_NULL(project);
char source_qn[CBM_SZ_512];
n = snprintf(source_qn, sizeof(source_qn), "%s.src.env", project);
ASSERT_GT(n, 0);
ASSERT_LT((size_t)n, sizeof(source_qn));
const char *env_qn = "__env__CBM_TEST_PARALLEL_ENV";
ASSERT_TRUE(pipeline_store_has_edge_between_qns(seq_db, project, source_qn, "CONFIGURES",
env_qn));
ASSERT_TRUE(pipeline_store_has_edge_between_qns(par_db, project, source_qn, "CONFIGURES",
env_qn));
char diff_err[CBM_SZ_8K] = {0};
int diff_rc = cbm_test_compare_canonical_graphs(seq_db, par_db, project, diff_err,
sizeof(diff_err));
if (diff_rc != 0) {
printf(" [parallel:env-access-diff] %s\n", diff_err);
}
ASSERT_EQ(diff_rc, 0);
free(project);
teardown_lang_repo();
PASS();
}
TEST(pipeline_parallel_channel_edges_target_channels) {
enum { FILLER_FILE_COUNT = 52, PARALLEL_WORKERS = 4 };
const char *files[] = {"app/main.py"};
@@ -13022,6 +13101,7 @@ SUITE(pipeline) {
RUN_TEST(pipeline_python_reexport_call_uses_resolved_import_edge);
RUN_TEST(pipeline_incremental_reexport_target_matches_full);
RUN_TEST(pipeline_parallel_duplicate_import_inherits_matches_sequential);
RUN_TEST(pipeline_parallel_env_access_matches_sequential);
RUN_TEST(pipeline_parallel_channel_edges_target_channels);
RUN_TEST(pipeline_go_type_classification);
RUN_TEST(pipeline_go_grouped_types);