From 3c2ee2141130762cfc3f19530f9d9f2808325741 Mon Sep 17 00:00:00 2001 From: Joseph Hunkeler Date: Mon, 29 Jun 2026 15:16:55 -0400 Subject: [PATCH 1/5] Refactor delivery_install.c to use wheel_*() library functions, instead of wheelinfo_*() file name parser. --- src/lib/core/include/wheel.h | 60 ++++- src/lib/core/wheel.c | 369 ++++++++++++++++++++-------- src/lib/delivery/delivery_install.c | 22 +- 3 files changed, 338 insertions(+), 113 deletions(-) diff --git a/src/lib/core/include/wheel.h b/src/lib/core/include/wheel.h index 765f2c37..fc64a5ff 100644 --- a/src/lib/core/include/wheel.h +++ b/src/lib/core/include/wheel.h @@ -12,6 +12,9 @@ #define WHEEL_FROM_DIST 0 #define WHEEL_FROM_METADATA 1 +#define WHEEL_MATCH_EXACT 0 ///< Match when all patterns are present +#define WHEEL_MATCH_ANY 1 ///< Match when any patterns are present + enum { WHEEL_META_METADATA_VERSION=0, WHEEL_META_NAME, @@ -135,6 +138,56 @@ struct Wheel { size_t num_entry_point; }; +#include +struct WheelDisplay { + struct WheelDistDisplay { + bool _enable; + bool _enable_header; + bool wheel_version; + bool generator; + bool root_is_pure_lib; + bool tag; + bool top_level; + bool zip_safe; + bool record; + bool entry_point; + } dist; + struct WheelMetadataDisplay { + bool _enable; + bool _enable_header; + bool metadata_version; + bool name; + bool version; + bool summary; + bool author; + bool author_email; + bool maintainer; + bool maintainer_email; + bool license; + bool license_expression; + bool home_page; + bool download_url; + bool project_url; + bool classifier; + bool requires_python; + bool requires_external; + bool description_content_type; + bool license_file; + bool import_name; + bool import_namespace; + bool requires_dist; + bool provides; + bool provides_dist; + bool obsoletes; + bool obsoletes_dist; + bool description; + bool platform; + bool supported_platform; + bool keywords; + bool dynamic; + bool provides_extra; + } metadata; +}; #define METADATA_MULTILINE_PREFIX " " @@ -249,9 +302,12 @@ int wheel_get_file_contents(const char *wheelfile, const char *filename, char ** /** * Display the values of a `Wheel` structure in human readable format * - * @param wheel + * @param wheel pointer to Wheel + * @param opt struct containing caller-configured display flags * @return 0 on success, -1 on error */ -int wheel_show_info(const struct Wheel *wheel); +int wheel_show_info(const struct Wheel *wheel, struct WheelDisplay opt); + +struct Wheel *wheel_search(const char *basepath, const char *name, char *to_match[], unsigned match_mode); #endif //WHEEL_H diff --git a/src/lib/core/wheel.c b/src/lib/core/wheel.c index bb8d1067..80f884b6 100644 --- a/src/lib/core/wheel.c +++ b/src/lib/core/wheel.c @@ -973,6 +973,7 @@ void wheel_metadata_free(struct WheelMetadata *meta) { guard_strlist_free(&meta->author_email); guard_strlist_free(&meta->author); + guard_strlist_free(&meta->dynamic); guard_strlist_free(&meta->maintainer); guard_strlist_free(&meta->maintainer_email); guard_strlist_free(&meta->requires_python); @@ -1214,133 +1215,234 @@ int wheel_value_error(struct WheelValue const *val) { return 0; } -int wheel_show_info(const struct Wheel *wheel) { - printf("WHEEL INFO\n\n"); - for (ssize_t i = 0; i < WHEEL_DIST_END_ENUM; i++) { - const char *key = wheel_get_key_by_id(WHEEL_FROM_DIST, i); - if (!key) { - SYSERROR("wheel_get_key_by_id(%zi) failed", i); - return -1; - } +bool *wheel_get_dist_display_map(struct WheelDisplay opt) { + bool *map = calloc(1, sizeof(struct WheelDisplay)); + if (!map) { + return NULL; + } + + map[WHEEL_DIST_VERSION] = opt.dist.wheel_version; + map[WHEEL_DIST_GENERATOR] = opt.dist.generator; + map[WHEEL_DIST_ROOT_IS_PURELIB] = opt.dist.root_is_pure_lib; + map[WHEEL_DIST_TAG] = opt.dist.tag; + map[WHEEL_DIST_ZIP_SAFE] = opt.dist.zip_safe; + map[WHEEL_DIST_TOP_LEVEL] = opt.dist.top_level; + map[WHEEL_DIST_ENTRY_POINT] = opt.dist.entry_point; + map[WHEEL_DIST_RECORD] = opt.dist.record; + + return map; +} + +bool *wheel_get_metadata_display_map(struct WheelDisplay opt) { + bool *map = calloc(1, sizeof(struct WheelDisplay)); + if (!map) { + return NULL; + } + map[WHEEL_META_METADATA_VERSION] = opt.metadata.version; + map[WHEEL_META_NAME] = opt.metadata.name; + map[WHEEL_META_VERSION] = opt.metadata.version; + map[WHEEL_META_AUTHOR] = opt.metadata.author; + map[WHEEL_META_AUTHOR_EMAIL] = opt.metadata.author_email; + map[WHEEL_META_MAINTAINER] = opt.metadata.maintainer; + map[WHEEL_META_MAINTAINER_EMAIL] = opt.metadata.maintainer_email; + map[WHEEL_META_SUMMARY] = opt.metadata.summary; + map[WHEEL_META_LICENSE] = opt.metadata.license; + map[WHEEL_META_LICENSE_EXPRESSION] = opt.metadata.license_expression; + map[WHEEL_META_LICENSE_FILE] = opt.metadata.license_file; + map[WHEEL_META_HOME_PAGE] = opt.metadata.home_page; + map[WHEEL_META_DOWNLOAD_URL] = opt.metadata.download_url; + map[WHEEL_META_PROJECT_URL] = opt.metadata.project_url; + map[WHEEL_META_CLASSIFIER] = opt.metadata.classifier; + map[WHEEL_META_REQUIRES_PYTHON] = opt.metadata.requires_python; + map[WHEEL_META_REQUIRES_EXTERNAL] = opt.metadata.requires_external; + map[WHEEL_META_IMPORT_NAME] = opt.metadata.import_name; + map[WHEEL_META_IMPORT_NAMESPACE] = opt.metadata.import_namespace; + map[WHEEL_META_REQUIRES_DIST] = opt.metadata.requires_dist; + map[WHEEL_META_PROVIDES] = opt.metadata.provides; + map[WHEEL_META_PROVIDES_DIST] = opt.metadata.provides_dist; + map[WHEEL_META_PROVIDES_EXTRA] = opt.metadata.provides_extra; + map[WHEEL_META_OBSOLETES] = opt.metadata.obsoletes; + map[WHEEL_META_OBSOLETES_DIST] = opt.metadata.obsoletes_dist; + map[WHEEL_META_PLATFORM] = opt.metadata.platform; + map[WHEEL_META_SUPPORTED_PLATFORM] = opt.metadata.platform; + map[WHEEL_META_KEYWORDS] = opt.metadata.keywords; + map[WHEEL_META_DYNAMIC] = opt.metadata.dynamic; + map[WHEEL_META_DESCRIPTION_CONTENT_TYPE] = opt.metadata.description_content_type; + map[WHEEL_META_DESCRIPTION] = opt.metadata.description; + + return map; +} - printf("%s: ", key); - fflush(stdout); - const struct WheelValue dist = wheel_get_value_by_id(wheel, WHEEL_FROM_DIST, i); - if (wheel_value_error(&dist)) { - SYSERROR("wheel_get_value_by_id(%zi) failed", i); +int wheel_show_info(const struct Wheel *wheel, struct WheelDisplay opt) { + if (opt.dist._enable) { + if (opt.dist._enable_header) { + printf("WHEEL INFO\n\n"); + } + bool *optmap = wheel_get_dist_display_map(opt); + if (!optmap) { + SYSERROR("wheel_get_dist_display_map failed"); return -1; } - switch (dist.type) { - case WHEELVAL_STR: { - char *s = dist.data; - if (s != NULL && !isempty(s)) { - printf("%s\n", s); - } else { - printf("[N/A]\n"); + + for (ssize_t i = 0; i < WHEEL_DIST_END_ENUM; i++) { + if (optmap[i]) { + const char *key = wheel_get_key_by_id(WHEEL_FROM_DIST, i); + if (!key) { + SYSERROR("wheel_get_key_by_id(%zi) failed", i); + guard_free(optmap); + return -1; } - break; - } - case WHEELVAL_STRLIST: { - struct StrList *list = dist.data; - if (list) { - printf("\n"); - for (size_t x = 0; x < strlist_count(list); x++) { - const char *item = strlist_item(list, x); - printf(" %s\n", item); - } - } else { - printf("[N/A]\n"); + + const struct WheelValue dist = wheel_get_value_by_id(wheel, WHEEL_FROM_DIST, i); + if (wheel_value_error(&dist)) { + SYSERROR("wheel_get_value_by_id(%zi) failed", i); + guard_free(optmap); + return -1; } - break; - } - case WHEELVAL_OBJ_RECORD: { - struct WheelRecord **record = dist.data; - if (record && *record) { - printf("\n"); - for (size_t x = 0; x < dist.count; x++) { - printf(" [%zu] %s (size: %zu bytes, checksum: %s)\n", x, wheel->record[x]->filename, wheel->record[x]->size, strlen(wheel->record[x]->checksum) ? wheel->record[x]->checksum : "N/A"); - } - } else { - printf("[N/A]\n"); + + if (isempty(dist.data)) { + // skip printing when no value is set + continue; } - break; - } - case WHEELVAL_OBJ_ENTRY_POINT: { - struct WheelEntryPoint **entry = dist.data; - if (entry && *entry) { - printf("\n"); - for (size_t x = 0; x < dist.count; x++) { - printf(" [%zu] type: %s, name: %s, function: %s\n", x, entry[x]->type, entry[x]->name, entry[x]->function); + + printf("%s: ", key); + fflush(stdout); + + switch (dist.type) { + case WHEELVAL_STR: { + char *s = dist.data; + if (s != NULL && !isempty(s)) { + printf("%s\n", s); + } else { + printf("[N/A]\n"); + } + break; } - } else { - printf("[N/A]\n"); + case WHEELVAL_STRLIST: { + struct StrList *list = dist.data; + if (list) { + printf("\n"); + for (size_t x = 0; x < strlist_count(list); x++) { + const char *item = strlist_item(list, x); + printf(" %s\n", item); + } + } else { + printf("[N/A]\n"); + } + break; + } + case WHEELVAL_OBJ_RECORD: { + struct WheelRecord **record = dist.data; + if (record && *record) { + printf("\n"); + for (size_t x = 0; x < dist.count; x++) { + printf(" [%zu] %s (size: %zu bytes, checksum: %s)\n", x, wheel->record[x]->filename, wheel->record[x]->size, strlen(wheel->record[x]->checksum) ? wheel->record[x]->checksum : "N/A"); + } + } else { + printf("[N/A]\n"); + } + break; + } + case WHEELVAL_OBJ_ENTRY_POINT: { + struct WheelEntryPoint **entry = dist.data; + if (entry && *entry) { + printf("\n"); + for (size_t x = 0; x < dist.count; x++) { + printf(" [%zu] type: %s, name: %s, function: %s\n", x, entry[x]->type, entry[x]->name, entry[x]->function); + } + } else { + printf("[N/A]\n"); + } + break; + } + default: + printf("[no handler]\n"); + break; } - break; } - default: - printf("[no handler]\n"); - break; } - + guard_free(optmap); } - printf("\nPACKAGE INFO\n\n"); - for (ssize_t i = 0; i < WHEEL_META_END_ENUM; i++) { - const char *key = wheel_get_key_by_id(WHEEL_FROM_METADATA, i); - if (!key) { - SYSERROR("wheel_get_key_by_id(%zi) failed", i); + if (opt.metadata._enable) { + bool *optmap = wheel_get_metadata_display_map(opt); + if (!optmap) { + SYSERROR("wheel_get_metadata_display_map failed"); return -1; } - printf("%s: ", key); - fflush(stdout); - const struct WheelValue pkg = wheel_get_value_by_id(wheel, WHEEL_FROM_METADATA, i); - if (wheel_value_error(&pkg)) { - SYSERROR("wheel_get_value_by_id(%zi) failed", i); - return -1; + if (opt.metadata._enable_header) { + printf("\nPACKAGE INFO\n\n"); } - switch (pkg.type) { - case WHEELVAL_STR: { - char *s = pkg.data; - if (s != NULL && !isempty(s)) { - printf("%s\n", s); - } else { - printf("[N/A]\n"); + + for (ssize_t i = 0; i < WHEEL_META_END_ENUM; i++) { + if (optmap[i]) { + const char *key = wheel_get_key_by_id(WHEEL_FROM_METADATA, i); + if (!key) { + SYSERROR("wheel_get_key_by_id(%zi) failed", i); + guard_free(optmap); + return -1; } - break; - } - case WHEELVAL_STRLIST: { - struct StrList *list = pkg.data; - if (list) { - printf("\n"); - for (size_t x = 0; x < strlist_count(list); x++) { - const char *item = strlist_item(list, x); - printf(" %s\n", item); - } - } else { - printf("[N/A]\n"); + + const struct WheelValue pkg = wheel_get_value_by_id(wheel, WHEEL_FROM_METADATA, i); + if (wheel_value_error(&pkg)) { + SYSERROR("wheel_get_value_by_id(%zi) failed", i); + guard_free(optmap); + return -1; } - break; - } - case WHEELVAL_OBJ_EXTRA: { - const struct WheelMetadata_ProvidesExtra **extra = pkg.data; - printf("\n"); - if (*extra) { - for (size_t x = 0; extra[x] != NULL; x++) { - printf(" + %s\n", extra[x]->target); - for (size_t z = 0; z < strlist_count(extra[x]->requires_dist); z++) { - const char *item = strlist_item(extra[x]->requires_dist, z); - printf(" `- %s\n", item); + + if (isempty(pkg.data)) { + // skip printing when no value is set + continue; + } + + printf("%s: ", key); + fflush(stdout); + switch (pkg.type) { + case WHEELVAL_STR: { + char *s = pkg.data; + if (s != NULL && !isempty(s)) { + printf("%s\n", s); + } else { + printf("[N/A]\n"); } + break; } - } else { - printf("[N/A]\n"); + case WHEELVAL_STRLIST: { + struct StrList *list = pkg.data; + if (list) { + printf("\n"); + for (size_t x = 0; x < strlist_count(list); x++) { + const char *item = strlist_item(list, x); + printf(" %s\n", item); + } + } else { + printf("[N/A]\n"); + } + break; + } + case WHEELVAL_OBJ_EXTRA: { + const struct WheelMetadata_ProvidesExtra **extra = pkg.data; + printf("\n"); + if (*extra) { + for (size_t x = 0; extra[x] != NULL; x++) { + printf(" + %s\n", extra[x]->target); + for (size_t z = 0; z < strlist_count(extra[x]->requires_dist); z++) { + const char *item = strlist_item(extra[x]->requires_dist, z); + printf(" `- %s\n", item); + } + } + } else { + printf("[N/A]\n"); + } + break; + } + default: + break; } - break; } - default: - break; } + guard_free(optmap); } return 0; } @@ -1397,3 +1499,62 @@ int wheel_package(struct Wheel **pkg, const char *filename) { } +struct Wheel *wheel_search(const char *basepath, const char *name, char *to_match[], unsigned match_mode) { + struct dirent *rec; + struct Wheel *result = NULL; + char package_path[PATH_MAX]; + char package_name[NAME_MAX]; + + safe_strncpy(package_name, name, sizeof(package_name)); + tolower_s(package_name); + snprintf(package_path, sizeof(package_path), "%s/%s", basepath, package_name); + + DIR *dp = opendir(package_path); + if (!dp) { + return NULL; + } + + while ((rec = readdir(dp)) != NULL) { + if (!strcmp(rec->d_name, ".") || !strcmp(rec->d_name, "..")) { + continue; + } + + char filename[NAME_MAX]; + safe_strncpy(filename, rec->d_name, sizeof(filename)); + + char *ext = strstr(filename, ".whl"); + if (ext) { + *ext = '\0'; + } else { + // not a wheel file. nothing to do + continue; + } + + size_t match = 0; + size_t pattern_count = 0; + for (; to_match[pattern_count] != NULL; pattern_count++) { + if (strstr(filename, to_match[pattern_count])) { + match++; + } + } + + if (!startswith(rec->d_name, name)) { + continue; + } + + if (match_mode == WHEEL_MATCH_EXACT && match != pattern_count) { + continue; + } + + char fullpath[PATH_MAX * 2] = {0}; + snprintf(fullpath, sizeof(fullpath), "%s/%s", package_path, rec->d_name); + if (wheel_package(&result, fullpath) < 0) { + SYSERROR("Unable to parse wheel package: %s", rec->d_name); + closedir(dp); + return NULL; + } + break; + } + closedir(dp); + return result; +} \ No newline at end of file diff --git a/src/lib/delivery/delivery_install.c b/src/lib/delivery/delivery_install.c index bb990145..b03086bb 100644 --- a/src/lib/delivery/delivery_install.c +++ b/src/lib/delivery/delivery_install.c @@ -1,6 +1,6 @@ #include "delivery.h" #include "conda.h" -#include "wheelinfo.h" +#include "wheel.h" #include "version_compare.h" static struct Test *requirement_from_test(struct Delivery *ctx, const char *name) { @@ -422,7 +422,7 @@ int delivery_install_packages(struct Delivery *ctx, char *conda_install_dir, cha SYSDEBUG("Tokenizing repository info tag: %s", info->repository_info_tag); strlist_append_tokenize(tag_data, info->repository_info_tag, "-"); - struct WheelInfo *whl = NULL; + struct Wheel *whl = NULL; char *post_commit = NULL; char *hash = NULL; if (strlist_count(tag_data) > 1) { @@ -437,7 +437,7 @@ int delivery_install_packages(struct Delivery *ctx, char *conda_install_dir, cha // etc. errno = 0; SYSDEBUG("%s", "Getting wheel information"); - whl = wheelinfo_get(ctx->storage.wheel_artifact_dir, info->name, + whl = wheel_search(ctx->storage.wheel_artifact_dir, info->name, (char *[]) {ctx->meta.python_compact, ctx->system.arch, "none", "any", post_commit, hash, @@ -451,13 +451,21 @@ int delivery_install_packages(struct Delivery *ctx, char *conda_install_dir, cha SYSERROR("No wheel packages found that match the description of '%s'", info->name); } else { // found, replace the original version with newly detected version - SYSDEBUG("Replacing version: %s", whl->version); + SYSDEBUG("Replacing version: %s", whl->metadata->version); guard_free(info->version); - info->version = strdup(whl->version); - SYSDEBUG("Version replaced with: %s", whl->version); + info->version = strdup(whl->metadata->version); + SYSDEBUG("Version replaced with: %s", info->version); } guard_strlist_free(&tag_data); - wheelinfo_free(&whl); + struct WheelDisplay si_opt; + memset(&si_opt, true, sizeof(si_opt)); + // Disable file record overview (too long) + si_opt.dist.record = false; + // Disable package description output (too long) + si_opt.metadata.description = false; + + wheel_show_info(whl, si_opt); + wheel_package_free(&whl); } char req[255] = {0}; From 87dafad4fc23d7a3a2fdd939738484bbb2a5a8bc Mon Sep 17 00:00:00 2001 From: Joseph Hunkeler Date: Tue, 30 Jun 2026 09:20:02 -0400 Subject: [PATCH 2/5] Add WheelDisplay argument to wheel_show_info call --- tests/test_wheel.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_wheel.c b/tests/test_wheel.c index c9aff6af..3571853e 100644 --- a/tests/test_wheel.c +++ b/tests/test_wheel.c @@ -36,7 +36,9 @@ static void test_wheel_package() { STASIS_ASSERT(wheel->metadata->metadata_version != NULL, "Metadata::version (of metadata) cannot be NULL"); // Implied test against key/id getters. If wheel_show_info segfaults, that functionality is broken. - STASIS_ASSERT(wheel_show_info(wheel) == 0, "wheel_show_info should never fail. Enum(s) might be out of sync with META_*_KEYS array(s)"); + struct WheelDisplay si_opt; + memset(&si_opt, true, sizeof(si_opt)); + STASIS_ASSERT(wheel_show_info(wheel, si_opt) == 0, "wheel_show_info should never fail. Enum(s) might be out of sync with META_*_KEYS array(s)"); // Get data from DIST const struct WheelValue dist_version = wheel_get_value_by_name(wheel, WHEEL_FROM_DIST, "Wheel-Version"); From c713536f1432b2af5624aecee2221733a6051ea4 Mon Sep 17 00:00:00 2001 From: Joseph Hunkeler Date: Tue, 30 Jun 2026 09:19:15 -0400 Subject: [PATCH 3/5] Remove wheelinfo.c, wheelinfo.h, test_wheelinfo.c * And references --- src/lib/core/CMakeLists.txt | 1 - src/lib/core/include/wheelinfo.h | 36 --------- src/lib/core/wheelinfo.c | 131 ------------------------------- tests/test_wheelinfo.c | 91 --------------------- 4 files changed, 259 deletions(-) delete mode 100644 src/lib/core/include/wheelinfo.h delete mode 100644 src/lib/core/wheelinfo.c delete mode 100644 tests/test_wheelinfo.c diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt index 3a54d94e..7df4dd2b 100644 --- a/src/lib/core/CMakeLists.txt +++ b/src/lib/core/CMakeLists.txt @@ -12,7 +12,6 @@ add_library(stasis_core STATIC download.c recipe.c relocation.c - wheelinfo.c wheel.c copy.c artifactory.c diff --git a/src/lib/core/include/wheelinfo.h b/src/lib/core/include/wheelinfo.h deleted file mode 100644 index 8009e91f..00000000 --- a/src/lib/core/include/wheelinfo.h +++ /dev/null @@ -1,36 +0,0 @@ -//! @file wheel.h -#ifndef STASIS_WHEEL_H -#define STASIS_WHEEL_H - -#include -#include -#include -#include "str.h" -#define WHEEL_MATCH_EXACT 0 ///< Match when all patterns are present -#define WHEEL_MATCH_ANY 1 ///< Match when any patterns are present - -struct WheelInfo { - char *distribution; ///< Package name - char *version; ///< Package version - char *build_tag; ///< Package build tag (optional) - char *python_tag; ///< Package Python tag (pyXY) - char *abi_tag; ///< Package ABI tag (cpXY, abiX, none) - char *platform_tag; ///< Package platform tag (linux_x86_64, any) - char *path_name; ///< Path to package on-disk - char *file_name; ///< Name of package on-disk -}; - -/** - * Extract metadata from a Python Wheel file name - * - * @param basepath directory containing a wheel file - * @param name of wheel file - * @param to_match a NULL terminated array of patterns (i.e. platform, arch, version, etc) - * @param match_mode WHEEL_MATCH_EXACT - * @param match_mode WHEEL_MATCH ANY - * @return pointer to populated Wheel on success - * @return NULL on error - */ -struct WheelInfo *wheelinfo_get(const char *basepath, const char *name, char *to_match[], unsigned match_mode); -void wheelinfo_free(struct WheelInfo **wheel); -#endif //STASIS_WHEEL_H diff --git a/src/lib/core/wheelinfo.c b/src/lib/core/wheelinfo.c deleted file mode 100644 index fe426aa5..00000000 --- a/src/lib/core/wheelinfo.c +++ /dev/null @@ -1,131 +0,0 @@ -#include "wheelinfo.h" - -struct WheelInfo *wheelinfo_get(const char *basepath, const char *name, char *to_match[], unsigned match_mode) { - struct dirent *rec; - struct WheelInfo *result = NULL; - char package_path[PATH_MAX]; - char package_name[NAME_MAX]; - - safe_strncpy(package_name, name, sizeof(package_name)); - tolower_s(package_name); - snprintf(package_path, sizeof(package_path), "%s/%s", basepath, package_name); - - DIR *dp = opendir(package_path); - if (!dp) { - return NULL; - } - - while ((rec = readdir(dp)) != NULL) { - if (!strcmp(rec->d_name, ".") || !strcmp(rec->d_name, "..")) { - continue; - } - - char filename[NAME_MAX]; - safe_strncpy(filename, rec->d_name, sizeof(filename)); - - char *ext = strstr(filename, ".whl"); - if (ext) { - *ext = '\0'; - } else { - // not a wheel file. nothing to do - continue; - } - - size_t match = 0; - size_t pattern_count = 0; - for (; to_match[pattern_count] != NULL; pattern_count++) { - if (strstr(filename, to_match[pattern_count])) { - match++; - } - } - - if (!startswith(rec->d_name, name)) { - continue; - } - - if (match_mode == WHEEL_MATCH_EXACT && match != pattern_count) { - continue; - } - - result = calloc(1, sizeof(*result)); - if (!result) { - SYSERROR("Unable to allocate %zu bytes for wheel struct", sizeof(*result)); - closedir(dp); - return NULL; - } - - result->path_name = realpath(package_path, NULL); - if (!result->path_name) { - SYSERROR("Unable to resolve absolute path to %s: %s", filename, strerror(errno)); - wheelinfo_free(&result); - closedir(dp); - return NULL; - } - result->file_name = strdup(rec->d_name); - if (!result->file_name) { - SYSERROR("Unable to allocate bytes for %s: %s", rec->d_name, strerror(errno)); - wheelinfo_free(&result); - closedir(dp); - return NULL; - } - - size_t parts_total; - char **parts = split(filename, "-", 0); - if (!parts) { - // This shouldn't happen unless a wheel file is present in the - // directory with a malformed file name, or we've managed to - // exhaust the system's memory - SYSERROR("%s has no '-' separators! (Delete this file and try again)", filename); - wheelinfo_free(&result); - closedir(dp); - return NULL; - } - - for (parts_total = 0; parts[parts_total] != NULL; parts_total++) {} - if (parts_total == 5) { - // no build tag - result->distribution = strdup(parts[0]); - result->version = strdup(parts[1]); - result->build_tag = NULL; - result->python_tag = strdup(parts[2]); - result->abi_tag = strdup(parts[3]); - result->platform_tag = strdup(parts[4]); - } else if (parts_total == 6) { - // has build tag - result->distribution = strdup(parts[0]); - result->version = strdup(parts[1]); - result->build_tag = strdup(parts[2]); - result->python_tag = strdup(parts[3]); - result->abi_tag = strdup(parts[4]); - result->platform_tag = strdup(parts[5]); - } else { - SYSERROR("Unknown wheel name format: %s. Expected 5 or 6 strings " - "separated by '-', but got %zu instead", filename, parts_total); - guard_array_free(parts); - wheelinfo_free(&result); - closedir(dp); - return NULL; - } - guard_array_free(parts); - break; - } - closedir(dp); - return result; -} - -void wheelinfo_free(struct WheelInfo **wheel) { - struct WheelInfo *w = (*wheel); - if (!w) { - return; - } - guard_free(w->path_name); - guard_free(w->file_name); - guard_free(w->distribution); - guard_free(w->version); - guard_free(w->build_tag); - guard_free(w->python_tag); - guard_free(w->abi_tag); - guard_free(w->python_tag); - guard_free(w->platform_tag); - guard_free(w); -} diff --git a/tests/test_wheelinfo.c b/tests/test_wheelinfo.c deleted file mode 100644 index 1abbeac8..00000000 --- a/tests/test_wheelinfo.c +++ /dev/null @@ -1,91 +0,0 @@ -#include "testing.h" -#include "wheelinfo.h" - -void test_wheelinfo_get() { - struct testcase { - const char *filename; - struct WheelInfo expected; - }; - struct testcase tc[] = { - { - // Test for "build tags" - .filename = "btpackage-1.2.3-mytag-py2.py3-none-any.whl", - .expected = { - .file_name = "btpackage-1.2.3-mytag-py2.py3-none-any.whl", - .version = "1.2.3", - .distribution = "btpackage", - .build_tag = "mytag", - .platform_tag = "any", - .python_tag = "py2.py3", - .abi_tag = "none", - .path_name = ".", - } - }, - { - // Test for universal package format - .filename = "anypackage-1.2.3-py2.py3-none-any.whl", - .expected = { - .file_name = "anypackage-1.2.3-py2.py3-none-any.whl", - .version = "1.2.3", - .distribution = "anypackage", - .build_tag = NULL, - .platform_tag = "any", - .python_tag = "py2.py3", - .abi_tag = "none", - .path_name = ".", - } - }, - { - // Test for binary package format - .filename = "binpackage-1.2.3-cp311-cp311-linux_x86_64.whl", - .expected = { - .file_name = "binpackage-1.2.3-cp311-cp311-linux_x86_64.whl", - .version = "1.2.3", - .distribution = "binpackage", - .build_tag = NULL, - .platform_tag = "linux_x86_64", - .python_tag = "cp311", - .abi_tag = "cp311", - .path_name = ".", - } - }, - }; - - struct WheelInfo *doesnotexist = wheelinfo_get("doesnotexist", "doesnotexist-0.0.1-py2.py3-none-any.whl", (char *[]) {"not", NULL}, WHEEL_MATCH_ANY); - STASIS_ASSERT(doesnotexist == NULL, "returned non-NULL on error"); - - for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { - struct testcase *test = &tc[i]; - struct WheelInfo *wheel = wheelinfo_get(".", test->expected.distribution, (char *[]) {(char *) test->expected.version, NULL}, WHEEL_MATCH_ANY); - STASIS_ASSERT(wheel != NULL, "result should not be NULL!"); - STASIS_ASSERT(wheel->file_name && strcmp(wheel->file_name, test->expected.file_name) == 0, "mismatched file name"); - STASIS_ASSERT(wheel->version && strcmp(wheel->version, test->expected.version) == 0, "mismatched version"); - STASIS_ASSERT(wheel->distribution && strcmp(wheel->distribution, test->expected.distribution) == 0, "mismatched distribution (package name)"); - STASIS_ASSERT(wheel->platform_tag && strcmp(wheel->platform_tag, test->expected.platform_tag) == 0, "mismatched platform tag ([platform]_[architecture])"); - STASIS_ASSERT(wheel->python_tag && strcmp(wheel->python_tag, test->expected.python_tag) == 0, "mismatched python tag (python version)"); - STASIS_ASSERT(wheel->abi_tag && strcmp(wheel->abi_tag, test->expected.abi_tag) == 0, "mismatched abi tag (python compatibility version)"); - if (wheel->build_tag) { - STASIS_ASSERT(strcmp(wheel->build_tag, test->expected.build_tag) == 0, - "mismatched build tag (optional arbitrary string)"); - } - wheelinfo_free(&wheel); - } -} - -int main(int argc, char *argv[]) { - STASIS_TEST_BEGIN_MAIN(); - STASIS_TEST_FUNC *tests[] = { - test_wheelinfo_get, - }; - - // Create mock package directories, and files - mkdir("binpackage", 0755); - touch("binpackage/binpackage-1.2.3-cp311-cp311-linux_x86_64.whl"); - mkdir("anypackage", 0755); - touch("anypackage/anypackage-1.2.3-py2.py3-none-any.whl"); - mkdir("btpackage", 0755); - touch("btpackage/btpackage-1.2.3-mytag-py2.py3-none-any.whl"); - - STASIS_TEST_RUN(tests); - STASIS_TEST_END_MAIN(); -} \ No newline at end of file From cf5a884d271527a108c6bda3751993b925609595 Mon Sep 17 00:00:00 2001 From: Joseph Hunkeler Date: Tue, 30 Jun 2026 09:20:59 -0400 Subject: [PATCH 4/5] test_wheel.c: Update test structure to exclude workspace creation * Fix doubled call to enable headless mode --- tests/test_wheel.c | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/tests/test_wheel.c b/tests/test_wheel.c index 3571853e..c2547572 100644 --- a/tests/test_wheel.c +++ b/tests/test_wheel.c @@ -4,10 +4,7 @@ #include "str.h" #include "wheel.h" -char cwd_start[PATH_MAX]; -char cwd_workspace[PATH_MAX]; int conda_is_installed = 0; -static char conda_prefix[PATH_MAX] = {0}; struct Delivery ctx; static const char *testpkg_filename = "testpkg/dist/testpkg-1.0.0-py3-none-any.whl"; @@ -113,18 +110,6 @@ int main(int argc, char *argv[]) { test_wheel_package, }; - char ws[] = "workspace_XXXXXX"; - if (!mkdtemp(ws)) { - SYSERROR("mkdtemp failed: %s, %s", ws, strerror(errno)); - exit(1); - } - getcwd(cwd_start, sizeof(cwd_start) - 1); - mkdir(ws, 0755); - chdir(ws); - getcwd(cwd_workspace, sizeof(cwd_workspace) - 1); - - snprintf(conda_prefix, strlen(cwd_workspace) + strlen("conda") + 2, "%s/conda", cwd_workspace); - const char *mockinidata = "[meta]\n" "name = mock\n" "version = 1.0.0\n" @@ -144,7 +129,7 @@ int main(int argc, char *argv[]) { const char *sysconfdir = getenv("STASIS_SYSCONFDIR"); globals.sysconfdir = strdup(sysconfdir ? sysconfdir : STASIS_SYSCONFDIR); - ctx.storage.root = strdup(cwd_workspace); + ctx.storage.root = strdup(TEST_WORKSPACE_DIR); char *cfgfile = join((char *[]) {globals.sysconfdir, "stasis.ini", NULL}, "/"); if (!cfgfile) { SYSERROR("unable to create path to global config"); @@ -188,11 +173,6 @@ int main(int argc, char *argv[]) { SYSERROR("conda_exec failed"); exit(1); } - - if (conda_setup_headless(&ctx.conda.capabilities)) { - SYSERROR("conda_setup_headless failed"); - exit(1); - } if (conda_env_create("testpkg", ctx.meta.python, NULL)) { SYSERROR("conda_env_create failed"); exit(1); @@ -206,13 +186,6 @@ int main(int argc, char *argv[]) { STASIS_TEST_RUN(tests); - if (chdir(cwd_start) < 0) { - SYSERROR("chdir failed: %s, %s", cwd_start, strerror(errno)); - exit(1); - } - if (rmtree(cwd_workspace)) { - SYSERROR("rmtree failed: %s, %s", cwd_workspace, strerror(errno)); - } delivery_free(&ctx); globals_free(); From 2f18f2e68927946d13933419d4f66540d16c867d Mon Sep 17 00:00:00 2001 From: Joseph Hunkeler Date: Tue, 30 Jun 2026 12:19:40 -0400 Subject: [PATCH 5/5] Split RT tests from Unit tests * Enable verbose output for RT side * Truncate huge skyscraper-length html output --- .github/workflows/cmake-multi-platform.yml | 31 +++++++++++++++++++--- tests/setup.sh | 22 ++++++++++++--- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index 2191559e..9ab157c1 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -68,8 +68,8 @@ jobs: -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DASAN=ON -DTESTS=ON - -DTESTS_VERBOSE=ON -DTESTS_RT=ON + -DTESTS_VERBOSE=ON -DDEBUG_MESSAGES=ON -S ${{ github.workspace }} @@ -78,7 +78,25 @@ jobs: PKG_CONFIG_PATH: /opt/homebrew/lib/pkgconfig:/usr/lib/x86_64-linux-gnu/pkgconfig run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} - - name: Test + - name: Test (Unit) + working-directory: ${{ steps.strings.outputs.build-output-dir }} + run: | + export ASAN_OPTIONS="verify_asan_link_order=0 strict_string_checks=1 detect_stack_use_after_return=1" + if [[ "$RUNNER_OS" == "macOS" ]]; then + ASAN_OPTIONS="$ASAN_OPTIONS detect_leaks=0" + else + ASAN_OPTIONS="$ASAN_OPTIONS detect_leaks=1" + fi + ctest --tests-regex '^test_.*' \ + --build-config ${{ matrix.build_type }} \ + --output-on-failure \ + --output-junit results_unit.xml \ + --test-output-size-passed 65536 \ + --test-output-size-failed 65536 + env: + STASIS_SYSCONFDIR: ../../.. + + - name: Test (RT) working-directory: ${{ steps.strings.outputs.build-output-dir }} run: | export ASAN_OPTIONS="verify_asan_link_order=0 strict_string_checks=1 detect_stack_use_after_return=1" @@ -87,6 +105,13 @@ jobs: else ASAN_OPTIONS="$ASAN_OPTIONS detect_leaks=1" fi - ctest --build-config ${{ matrix.build_type }} --output-on-failure --output-junit results.xml --test-output-size-passed 65536 --test-output-size-failed 65536 + ctest --tests-regex '^rt_.*' \ + -V \ + --build-config ${{ matrix.build_type }} \ + --output-junit results_rt.xml \ + --test-output-size-passed 65536 \ + --test-output-size-failed 65536 env: STASIS_SYSCONFDIR: ../../.. + STASIS_TEST_ALWAYS_SHOW_OUTPUT: 1 + STASIS_TEST_ALWAYS_SHOW_INDEXER_OUTPUT: 0 diff --git a/tests/setup.sh b/tests/setup.sh index bce2fbd7..c69d047a 100644 --- a/tests/setup.sh +++ b/tests/setup.sh @@ -102,6 +102,8 @@ install_stasis() { popd } +STASIS_TEST_ALWAYS_SHOW_OUTPUT=${STASIS_TEST_ALWAYS_SHOW_OUTPUT:-0} +STASIS_TEST_ALWAYS_SHOW_INDEXER_OUTPUT=${STASIS_TEST_ALWAYS_SHOW_INDEXER_OUTPUT:-0} STASIS_TEST_RESULT_FAIL=0 STASIS_TEST_RESULT_PASS=0 @@ -112,8 +114,14 @@ run_command() { local lines_on_error=1000 /bin/echo "Testing: $cmd " - $cmd &>"$logfile" - code=$? + if (( STASIS_TEST_ALWAYS_SHOW_OUTPUT )); then + $cmd + code=$? + else + $cmd &>"$logfile" + code=$? + fi + if (( code )); then if (( code == 127 )); then echo "... SKIP" @@ -226,7 +234,15 @@ check_output_indexed_dir() { echo echo "FILENAME: $x" echo - cat "$x" + if (( STASIS_TEST_ALWAYS_SHOW_INDEXER_OUTPUT )); then + cat "$x" + else + head "$x" + echo + echo '' + echo + tail "$x" + fi echo "[EOF]" echo done