diff --git a/src/pipeline/pass_definitions.c b/src/pipeline/pass_definitions.c index 0d723047c..75334535d 100644 --- a/src/pipeline/pass_definitions.c +++ b/src/pipeline/pass_definitions.c @@ -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) { diff --git a/src/pipeline/pass_parallel.c b/src/pipeline/pass_parallel.c index 59103104c..ab8ba070f 100644 --- a/src/pipeline/pass_parallel.c +++ b/src/pipeline/pass_parallel.c @@ -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); diff --git a/src/pipeline/pipeline_internal.h b/src/pipeline/pipeline_internal.h index ccd2e48f5..0bf56e55f 100644 --- a/src/pipeline/pipeline_internal.h +++ b/src/pipeline/pipeline_internal.h @@ -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); diff --git a/tests/test_pipeline.c b/tests/test_pipeline.c index f3718c70a..9d6be62f2 100644 --- a/tests/test_pipeline.c +++ b/tests/test_pipeline.c @@ -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 \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);