/* test/test_ncm_cache.c — M6 缓存命名「实际档」宿主单测(纯 libc,不依赖 mbedtls/jsmn)。 * * 覆盖(对应任务:命名生成/解析、降级后写入名、hires 命中已存 exhigh、旧命名兼容、LRU 淘汰): * ① ncm_cache_actual_level:请求档 + 容器/码率/位深 + 响应生效档 → 实际档(只降不升); * ② 命名生成:实际档键 → songs/song__<实际档>.<实际格式>(降级后不再写请求档名); * ③ 命中语义:hires 请求命中已存 song__exhigh.mp3 / song__lossless.flac(不重下), * 低档请求不向上命中高档实体; * ④ 旧命名兼容:song__hires.mp3(真机存量)可命中/可扫描/可删除,不迁移; * 历史双点名 song_..mp3 命中即迁移;LRU 淘汰对两种命名一视同仁; * ⑤ 已缓存列表去重:同曲同实际档的旧名+新名只展示最近访问的一条;不同实际档都展示; * ⑥ ncm_cache_level_from_path 解析(含 sky_c51 / 无档位段 / 封面名)。 * * 编译/运行($TMPDIR 纪律;Termux 的 /tmp 不可写):./build_ncm_cache_host.sh && ./test/test_ncm_cache_host */ #include #include #include #include #include #include #include #include #include #include "ncm_common.h" /* NCM_OK / NCM_ERR_* */ #include "ncm_cache.h" /* ---- ncm_cache.c 依赖的核心符号(宿主单测最小桩;与 core 实现同语义) ---- */ size_t ncm_strlcpy(char *dst, const char *src, size_t sz) { size_t n = 0; if (sz) { while (src[n] && n < sz - 1) { dst[n] = src[n]; n++; } dst[n] = 0; } while (src[n]) n++; return n; } int ncm_store_mkdir_p(const char *path) { if (!path || !*path) return -1; char tmp[512]; size_t len = strlen(path); if (len >= sizeof tmp) return -1; memcpy(tmp, path, len + 1); if (tmp[len - 1] == '/') tmp[len - 1] = '\0'; for (size_t i = 1; i <= strlen(tmp); i++) { if (tmp[i] == '/' || tmp[i] == '\0') { char ch = tmp[i]; tmp[i] = '\0'; if (tmp[0] && mkdir(tmp, 0755) != 0 && errno != EEXIST) return -1; tmp[i] = ch; } } return 0; } int ncm_store_read_cfg_int(const char *dir, const char *key, int def) { (void)dir; (void)key; return def; } const char *ncm_api_level_str(int level) { switch (level) { case NCM_LEVEL_SURROUND: case NCM_LEVEL_SKY: return "sky"; case NCM_LEVEL_JYMASTER: return "jymaster"; case NCM_LEVEL_JYEFFECT: return "jyeffect"; case NCM_LEVEL_DOLBY: return "dolby"; case NCM_LEVEL_HIRES: return "hires"; case NCM_LEVEL_LOSSLESS: return "lossless"; case NCM_LEVEL_EXHIGH: return "exhigh"; case NCM_LEVEL_HIGHER: return "higher"; default: return "standard"; } } const char *ncm_api_level_key(int level) { if (level == NCM_LEVEL_SURROUND) return "sky_c51"; return ncm_api_level_str(level); } int ncm_api_level_from_key(const char *key) { if (!key || !key[0]) return -1; if (!strcmp(key, "sky_c51")) return NCM_LEVEL_SURROUND; if (!strcmp(key, "sky")) return NCM_LEVEL_SKY; if (!strcmp(key, "jymaster")) return NCM_LEVEL_JYMASTER; if (!strcmp(key, "jyeffect")) return NCM_LEVEL_JYEFFECT; if (!strcmp(key, "dolby")) return NCM_LEVEL_DOLBY; if (!strcmp(key, "hires")) return NCM_LEVEL_HIRES; if (!strcmp(key, "lossless")) return NCM_LEVEL_LOSSLESS; if (!strcmp(key, "exhigh")) return NCM_LEVEL_EXHIGH; if (!strcmp(key, "higher")) return NCM_LEVEL_HIGHER; if (!strcmp(key, "standard")) return NCM_LEVEL_STANDARD; return -1; } static int g_fail; #define CHECK(cond, fmt, ...) do { \ if (cond) printf(" PASS " fmt "\n", ##__VA_ARGS__); \ else { printf(" FAIL " fmt "\n", ##__VA_ARGS__); g_fail = 1; } \ } while (0) #define TMPDIR_PREFIX "ncm_cache_test" static void fresh_dir(const char *sub) { char cmd[256]; snprintf(cmd, sizeof cmd, "rm -rf %s/%s; mkdir -p %s/%s", TMPDIR_PREFIX, sub, TMPDIR_PREFIX, sub); if (system(cmd) != 0) { /* best-effort */ } } /* ---- 样本:MP3 首帧(MPEG1 Layer III)+ 假音频字节 / FLAC STREAMINFO + 假帧 ---- */ static unsigned char *make_mp3(int kbps, size_t *len) { static unsigned char buf[512]; int idx = (kbps == 128) ? 9 : 14; /* 9=128k, 14=320k(MPEG1 L3 码率表) */ memset(buf, 0x11, sizeof buf); buf[0] = 0xFF; buf[1] = 0xFB; buf[2] = (unsigned char)((idx << 4) | 0x00); /* 44.1kHz(sr_idx=0) */ buf[3] = 0x00; *len = sizeof buf; return buf; } static unsigned char *make_flac(int bps, int sr, size_t *len) { static unsigned char buf[700]; size_t o = 0; memset(buf, 0, sizeof buf); memcpy(buf + o, "fLaC", 4); o += 4; buf[o++] = 0x80; buf[o++] = 0x00; buf[o++] = 0x00; buf[o++] = 34; /* STREAMINFO, last */ unsigned char *si = buf + o; si[0] = 0x10; si[1] = 0x00; si[2] = 0x10; si[3] = 0x00; si[10] = (unsigned char)((sr >> 12) & 0xFF); si[11] = (unsigned char)((sr >> 4) & 0xFF); si[12] = (unsigned char)(((sr & 0x0F) << 4) | 0x02 | (((bps - 1) >> 4) & 0x01)); si[13] = (unsigned char)(((bps - 1) & 0x0F) << 4); si[16] = 0xAC; si[17] = 0x44; o += 34; for (int i = 0; i < 512; i++) buf[o++] = (unsigned char)(0xF8 ^ i); *len = o; return buf; } static int file_put(const char *path, const void *data, size_t len) { FILE *f = fopen(path, "wb"); if (!f) return -1; if (len && fwrite(data, 1, len, f) != len) { fclose(f); return -1; } return fclose(f); } static int file_exists(const char *path) { struct stat st; return stat(path, &st) == 0 && S_ISREG(st.st_mode); } static int file_exists_cstr(const char *dir, const char *name) { char p[512]; snprintf(p, sizeof p, "%s/songs/%s", dir, name); return file_exists(p); } static void set_mtime(const char *path, long t) { struct timespec ts[2]; ts[0].tv_sec = t; ts[0].tv_nsec = 0; ts[1].tv_sec = t; ts[1].tv_nsec = 0; utimensat(AT_FDCWD, path, ts, 0); } /* 走生产写路径:实际档键 → ncm_cache_open/write/commit → 正式文件名。 */ static int cache_put(ncm_cache_t *c, const char *id, const char *level_key, const char *ext, const void *data, size_t len, char *out, size_t out_sz) { char key[64]; ncm_cache_writer_t *w; snprintf(key, sizeof key, "%s_%s", id, level_key); if (ncm_cache_open(c, key, ext, &w) != NCM_OK) return -1; if (len && ncm_cache_write(w, (const unsigned char *)data, len) != NCM_OK) { ncm_cache_abort(w); return -1; } if (ncm_cache_commit(w) != 0) return -1; return ncm_cache_path(c, key, ext, out, out_sz); } /* 直接落盘(旧命名/手工样本):songs/,不走 writer。 */ static void raw_put(const ncm_cache_t *c, const char *name, const void *data, size_t len) { char p[NCM_CACHE_DIR_MAX + 64]; snprintf(p, sizeof p, "%s/%s", ncm_cache_songs_dir(c), name); file_put(p, data, len); } /* ============================================================ */ static void test_actual_level(void) { ncm_audio_info_t ai; printf("== actual level mapping (single source of truth) ==\n"); memset(&ai, 0, sizeof ai); snprintf(ai.container, sizeof ai.container, "mp3"); ai.bitrate_kbps = 320; CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, -1) == NCM_LEVEL_EXHIGH, "hires + 320k mp3 -> exhigh"); CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, NCM_LEVEL_EXHIGH) == NCM_LEVEL_EXHIGH, "hires + 320k mp3 + chain eff exhigh -> exhigh"); CHECK(ncm_cache_actual_level(NCM_LEVEL_STANDARD, &ai, -1) == NCM_LEVEL_STANDARD, "standard request never upgraded by 320k mp3"); ai.bitrate_kbps = 128; CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, -1) == NCM_LEVEL_STANDARD, "hires + 128k mp3 -> standard"); ai.bitrate_kbps = 0; CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, -1) == NCM_LEVEL_EXHIGH, "hires + mp3 br unknown -> exhigh (mp3 never above exhigh)"); CHECK(ncm_cache_actual_level(NCM_LEVEL_SURROUND, &ai, -1) == NCM_LEVEL_EXHIGH, "surround + mp3 br unknown -> exhigh"); memset(&ai, 0, sizeof ai); snprintf(ai.container, sizeof ai.container, "flac"); ai.bits_per_sample = 16; ai.sample_rate = 44100; CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, -1) == NCM_LEVEL_LOSSLESS, "hires + 16bit/44.1k flac -> lossless"); ai.bits_per_sample = 24; ai.sample_rate = 48000; CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, -1) == NCM_LEVEL_HIRES, "hires + 24bit/48k flac -> hires"); ai.sample_rate = 96000; CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, -1) == NCM_LEVEL_HIRES, "hires + 24bit/96k flac -> hires (request caps)"); CHECK(ncm_cache_actual_level(NCM_LEVEL_JYMASTER, &ai, -1) == NCM_LEVEL_JYEFFECT, "jymaster + 24bit/96k flac -> jyeffect"); ai.sample_rate = 192000; CHECK(ncm_cache_actual_level(NCM_LEVEL_JYMASTER, &ai, -1) == NCM_LEVEL_JYMASTER, "jymaster + 24bit/192k flac -> jymaster"); memset(&ai, 0, sizeof ai); snprintf(ai.container, sizeof ai.container, "flac"); /* 仅知容器(响应时点) */ CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, NCM_LEVEL_LOSSLESS) == NCM_LEVEL_LOSSLESS, "hires + flac(header unknown) + chain eff lossless -> lossless"); CHECK(ncm_cache_actual_level(-1, &ai, NCM_LEVEL_HIRES) == NCM_LEVEL_HIRES, "unknown request + chain eff hires -> hires"); CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, NULL, -1) == NCM_LEVEL_HIRES, "no probe + unknown chain -> request level"); } static void test_naming_degraded_write(void) { ncm_cache_t c; char dir[256], path[NCM_CACHE_DIR_MAX + 64]; size_t mlen; unsigned char *mp3 = make_mp3(320, &mlen); ncm_audio_info_t ai; int actual; printf("== naming: degraded write uses actual level (M6) ==\n"); snprintf(dir, sizeof dir, TMPDIR_PREFIX "/naming"); fresh_dir("naming"); ncm_cache_init(&c, dir); memset(&ai, 0, sizeof ai); snprintf(ai.container, sizeof ai.container, "mp3"); ai.bitrate_kbps = 320; actual = ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, NCM_LEVEL_EXHIGH); CHECK(actual == NCM_LEVEL_EXHIGH, "degraded response actual level = exhigh"); CHECK(cache_put(&c, "171650", ncm_api_level_key(actual), "mp3", mp3, mlen, path, sizeof path) == 0, "write with actual-level key"); CHECK(strstr(path, "song_171650_exhigh.mp3") != NULL, "file name reflects actual level (%s)", path); CHECK(file_exists(path), "actual-named file on disk"); CHECK(!file_exists_cstr(dir, "song_171650_hires.mp3"), "no request-level name written for degraded download"); CHECK(ncm_cache_level_from_path(path) == NCM_LEVEL_EXHIGH, "level parsed from new file name = exhigh"); } static void test_hit_semantics(void) { ncm_cache_t c; char dir[256], path[NCM_CACHE_DIR_MAX + 64]; size_t mlen, flen; unsigned char *mp3 = make_mp3(320, &mlen); unsigned char *flac = make_flac(16, 44100, &flen); printf("== hit semantics: hires request finds stored actual-level entries ==\n"); snprintf(dir, sizeof dir, TMPDIR_PREFIX "/hit"); fresh_dir("hit"); ncm_cache_init(&c, dir); /* 已存:exhigh mp3(新命名)+ lossless flac(新命名) */ CHECK(cache_put(&c, "2001", "exhigh", "mp3", mp3, mlen, path, sizeof path) == 0, "store exhigh mp3"); CHECK(cache_put(&c, "2002", "lossless", "flac", flac, flen, path, sizeof path) == 0, "store lossless flac"); CHECK(ncm_cache_lookup(&c, "2001_hires", "mp3", path, sizeof path) == NCM_OK && strstr(path, "song_2001_exhigh.mp3") != NULL, "hires request hits stored exhigh mp3 (no redownload)"); CHECK(ncm_cache_level_from_path(path) == NCM_LEVEL_EXHIGH, "hit level = exhigh"); CHECK(ncm_cache_lookup(&c, "2002_hires", "flac", path, sizeof path) == NCM_OK && strstr(path, "song_2002_lossless.flac") != NULL, "hires request hits stored lossless flac"); CHECK(ncm_cache_lookup(&c, "2002_exhigh", "flac", path, sizeof path) == NCM_ERR_NOENT, "lower request never hits higher-tier entity (no upward lookup)"); CHECK(ncm_cache_lookup(&c, "2001_exhigh", "mp3", path, sizeof path) == NCM_OK, "exact level still hits directly"); CHECK(ncm_cache_lookup(&c, "2001_standard", "mp3", path, sizeof path) == NCM_ERR_NOENT, "standard request does not use 320k exhigh file"); CHECK(ncm_cache_lookup(&c, "9999_hires", "mp3", path, sizeof path) == NCM_ERR_NOENT, "unknown song misses"); } static void test_legacy_compat(void) { ncm_cache_t c; char dir[256], path[NCM_CACHE_DIR_MAX + 64]; size_t mlen; unsigned char *mp3 = make_mp3(320, &mlen); printf("== legacy naming compatibility (read/delete/scan, no migration) ==\n"); snprintf(dir, sizeof dir, TMPDIR_PREFIX "/legacy"); fresh_dir("legacy"); ncm_cache_init(&c, dir); /* 真机存量形态:请求档 hires + 实际 320k mp3 */ raw_put(&c, "song_171650_hires.mp3", mp3, mlen); CHECK(ncm_cache_lookup(&c, "171650_hires", "mp3", path, sizeof path) == NCM_OK && strstr(path, "song_171650_hires.mp3") != NULL, "legacy hires.mp3 still playable at its request key"); CHECK(file_exists(path), "legacy file untouched (not renamed)"); CHECK(ncm_cache_level_from_path(path) == NCM_LEVEL_HIRES, "path level is the legacy request level (hires)"); /* 扫描:旧文件的档位段保留(删除/点播定位用),实际档由文件头纠正 */ { ncm_cache_item_t items[8]; int n = ncm_cache_scan(&c, items, 8); CHECK(n == 1, "scan lists legacy entry (n=%d)", n); CHECK(n == 1 && strcmp(items[0].id, "171650") == 0 && strcmp(items[0].level, "hires") == 0 && strcmp(items[0].ext, "mp3") == 0, "legacy item id/level/ext parsed"); CHECK(n == 1 && items[0].actual_level == NCM_LEVEL_EXHIGH, "legacy hires.mp3 displays actual level exhigh (got %d)", n == 1 ? items[0].actual_level : -999); if (n == 1) CHECK(ncm_cache_remove(&c, items[0].id, items[0].level, items[0].ext) == NCM_OK, "delete by scanned legacy key"); CHECK(!file_exists_cstr(dir, "song_171650_hires.mp3"), "legacy file removed by list delete path"); } /* 历史双点名 song_..mp3:命中即迁移到单点名 */ raw_put(&c, "song_171651..mp3", mp3, mlen); CHECK(ncm_cache_lookup(&c, "171651", "mp3", path, sizeof path) == NCM_OK && strstr(path, "song_171651.mp3") != NULL && strstr(path, "..") == NULL, "legacy double-dot migrated on lookup (%s)", path); CHECK(!file_exists_cstr(dir, "song_171651..mp3"), "double-dot source gone"); /* 旧无档位键 song_.mp3:任何请求档都回退命中(调用方第 3 步 lookup(id)) */ CHECK(ncm_cache_lookup(&c, "171651", "mp3", path, sizeof path) == NCM_OK, "no-level legacy key still hit"); CHECK(ncm_cache_remove(&c, "171651", "", "mp3") == NCM_OK, "remove no-level legacy key"); } static void test_scan_dedup(void) { ncm_cache_t c; char dir[256], path[NCM_CACHE_DIR_MAX + 64]; size_t mlen, flen; unsigned char *mp3 = make_mp3(320, &mlen); unsigned char *flac = make_flac(16, 44100, &flen); printf("== cache list dedup: same song same actual level ==\n"); snprintf(dir, sizeof dir, TMPDIR_PREFIX "/dedup"); fresh_dir("dedup"); ncm_cache_init(&c, dir); /* 同曲同实际档、两种命名并存(旧 song_3001_hires.mp3 新 song_3001_exhigh.mp3) */ raw_put(&c, "song_3001_hires.mp3", mp3, mlen); raw_put(&c, "song_3001_exhigh.mp3", mp3, mlen); { char p[512]; snprintf(p, sizeof p, "%s/songs/song_3001_hires.mp3", dir); set_mtime(p, 1000); snprintf(p, sizeof p, "%s/songs/song_3001_exhigh.mp3", dir); set_mtime(p, 2000); } CHECK(cache_put(&c, "3002", "lossless", "flac", flac, flen, path, sizeof path) == 0, "store lossless entry for tier-variety case"); CHECK(cache_put(&c, "3002", "exhigh", "mp3", mp3, mlen, path, sizeof path) == 0, "store exhigh entry for same song"); { ncm_cache_item_t items[8]; int n = ncm_cache_scan(&c, items, 8); int d3001 = 0, d3002 = 0; int lv3001 = -999; for (int i = 0; i < n; i++) { if (strcmp(items[i].id, "3001") == 0) { d3001++; lv3001 = items[i].actual_level; } if (strcmp(items[i].id, "3002") == 0) d3002++; } CHECK(d3001 == 1, "same song same actual level deduped to 1 row (got %d)", d3001); CHECK(lv3001 == NCM_LEVEL_EXHIGH, "deduped row keeps actual level exhigh (got %d)", lv3001); CHECK(d3002 == 2, "different actual levels both listed (got %d)", d3002); } } static void test_lru_compat(void) { ncm_cache_t c; char dir[256]; size_t mlen; unsigned char *mp3 = make_mp3(320, &mlen); char oldp[512], newp[512]; int fd; printf("== LRU eviction compatibility (legacy + actual naming) ==\n"); snprintf(dir, sizeof dir, TMPDIR_PREFIX "/lru"); fresh_dir("lru"); ncm_cache_init(&c, dir); ncm_cache_set_limit_gb(&c, 1); /* 1GB 最小上限 */ /* 稀疏文件:旧命名 700MB(最旧)+ 新命名 400MB(最新)→ 超限删最旧 */ snprintf(oldp, sizeof oldp, "%s/songs/song_4001_hires.mp3", dir); snprintf(newp, sizeof newp, "%s/songs/song_4002_exhigh.mp3", dir); fd = open(oldp, O_WRONLY | O_CREAT, 0644); if (fd >= 0) { if (ftruncate(fd, 700L << 20) != 0) {} close(fd); } fd = open(newp, O_WRONLY | O_CREAT, 0644); if (fd >= 0) { if (ftruncate(fd, 400L << 20) != 0) {} close(fd); } set_mtime(oldp, 1000); set_mtime(newp, 2000); CHECK(ncm_cache_total_bytes(&c) == (700ULL << 20) + (400ULL << 20), "total counts both naming schemes (%llu)", (unsigned long long)ncm_cache_total_bytes(&c)); CHECK(ncm_cache_evict(&c) == NCM_OK, "evict runs"); CHECK(!file_exists(oldp), "oldest legacy-named entry evicted"); CHECK(file_exists(newp), "newest actual-named entry kept"); CHECK(ncm_cache_total_bytes(&c) == (400ULL << 20), "total after evict = 400MB (%llu)", (unsigned long long)ncm_cache_total_bytes(&c)); ncm_cache_set_limit_gb(&c, 10); /* 清空兼容两种命名 */ raw_put(&c, "song_4003_hires.mp3", mp3, mlen); raw_put(&c, "song_4004_exhigh.mp3", mp3, mlen); CHECK(ncm_cache_clear(&c) == NCM_OK, "clear runs"); CHECK(!file_exists(newp) && !file_exists_cstr(dir, "song_4003_hires.mp3") && !file_exists_cstr(dir, "song_4004_exhigh.mp3"), "clear removes both naming schemes"); } static void test_remove_compat(void) { ncm_cache_t c; char dir[256]; size_t mlen; unsigned char *mp3 = make_mp3(320, &mlen); printf("== remove compatibility ==\n"); snprintf(dir, sizeof dir, TMPDIR_PREFIX "/remove"); fresh_dir("remove"); ncm_cache_init(&c, dir); raw_put(&c, "song_5001_hires.mp3", mp3, mlen); /* 旧请求档名 */ raw_put(&c, "song_5002_exhigh.mp3", mp3, mlen); /* 新实际档名 */ raw_put(&c, "song_5003.mp3", mp3, mlen); /* 旧无档位键 */ CHECK(ncm_cache_remove(&c, "5001", "hires", "mp3") == NCM_OK, "remove legacy request-level name"); CHECK(ncm_cache_remove(&c, "5002", "exhigh", "mp3") == NCM_OK, "remove actual-level name"); CHECK(ncm_cache_remove(&c, "5003", "", "mp3") == NCM_OK, "remove no-level name"); CHECK(ncm_cache_remove(&c, "5002", "exhigh", "mp3") == NCM_ERR_NOENT, "second remove = NOENT"); } static void test_level_from_path(void) { printf("== level_from_path parsing ==\n"); CHECK(ncm_cache_level_from_path("/x/songs/song_171650_exhigh.mp3") == NCM_LEVEL_EXHIGH, "song__exhigh.mp3 -> exhigh"); CHECK(ncm_cache_level_from_path("song_171650_sky_c51.flac") == NCM_LEVEL_SURROUND, "song__sky_c51.flac -> surround"); CHECK(ncm_cache_level_from_path("song_171650.mp3") == -1, "no level segment -> -1"); CHECK(ncm_cache_level_from_path("cover_current.jpg") == -1, "cover name -> -1"); CHECK(ncm_cache_level_from_path("/tmp/pf_hires.mp3") == NCM_LEVEL_HIRES, "bare _.ext parsed"); CHECK(ncm_cache_level_from_path(NULL) == -1 && ncm_cache_level_from_path("") == -1, "NULL/empty -> -1"); } int main(void) { const char *base = getenv("TMPDIR"); printf("ncm_cache naming self-test (M6)\n"); if (base && base[0] && chdir(base) != 0) printf(" warn: chdir(%s) failed\n", base); { char cmd[128]; snprintf(cmd, sizeof cmd, "rm -rf %s; mkdir -p %s", TMPDIR_PREFIX, TMPDIR_PREFIX); if (system(cmd) != 0) { /* best-effort */ } } test_actual_level(); test_naming_degraded_write(); test_hit_semantics(); test_legacy_compat(); test_scan_dedup(); test_lru_compat(); test_remove_compat(); test_level_from_path(); printf("\n%s\n", g_fail ? "== RESULT: FAIL ==" : "== RESULT: PASS =="); return g_fail ? 1 : 0; }