|
| 1 | +name: ci |
| 2 | + |
| 3 | +on: |
| 4 | + push: |
| 5 | + pull_request: |
| 6 | + |
| 7 | +jobs: |
| 8 | + build-test: |
| 9 | + strategy: |
| 10 | + fail-fast: false |
| 11 | + matrix: |
| 12 | + os: [ubuntu-latest, macos-latest, windows-latest] |
| 13 | + runs-on: ${{ matrix.os }} |
| 14 | + steps: |
| 15 | + - uses: actions/checkout@v4 |
| 16 | + - uses: actions/setup-python@v5 |
| 17 | + with: |
| 18 | + python-version: "3.x" |
| 19 | + |
| 20 | + - name: Configure |
| 21 | + run: cmake -S . -B build -DFCE_ENABLE_ZSTD=ON |
| 22 | + |
| 23 | + - name: Build |
| 24 | + run: cmake --build build --config Release --parallel 2 |
| 25 | + |
| 26 | + - name: Generate C API smoke test |
| 27 | + shell: bash |
| 28 | + run: | |
| 29 | + mkdir -p ci_smoke |
| 30 | + cat > ci_smoke/CMakeLists.txt <<'EOF' |
| 31 | + cmake_minimum_required(VERSION 3.16) |
| 32 | + project(fce_ci_smoke LANGUAGES C) |
| 33 | + add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/.." fce) |
| 34 | + add_executable(ci_smoke ci_smoke.c) |
| 35 | + target_link_libraries(ci_smoke PRIVATE fast_cache_engine) |
| 36 | + EOF |
| 37 | + cat > ci_smoke/ci_smoke.c <<'EOF' |
| 38 | + #include "fce_cache.h" |
| 39 | + #include <stdint.h> |
| 40 | + #include <stdio.h> |
| 41 | + #include <string.h> |
| 42 | +
|
| 43 | + #define CHECK_OK(expr) do { FceStatus st__ = (expr); if (st__ != FCE_OK) { \ |
| 44 | + fprintf(stderr, "%s failed: %s\n", #expr, fce_status_string(st__)); return 1; } } while (0) |
| 45 | + #define CHECK(expr) do { if (!(expr)) { fprintf(stderr, "check failed: %s\n", #expr); return 1; } } while (0) |
| 46 | +
|
| 47 | + static int check_bytes(const void *p, size_t n, const char *s) { |
| 48 | + return n == strlen(s) && memcmp(p, s, n) == 0; |
| 49 | + } |
| 50 | +
|
| 51 | + int main(void) { |
| 52 | + const void *v = NULL; |
| 53 | + size_t n = 0; |
| 54 | + FceBuilder *b = NULL; |
| 55 | + FceReader *r = NULL; |
| 56 | + FceIterator *it = NULL; |
| 57 | +
|
| 58 | + FceSchema s = fce_schema_default(); |
| 59 | + s.allow_duplicate_put = 1; |
| 60 | + CHECK_OK(fce_builder_open("ci_sorted_cache", &s, &b)); |
| 61 | + CHECK_OK(fce_builder_put(b, "k-a13f", 6, "v-01", 4)); |
| 62 | + CHECK_OK(fce_builder_put(b, "k-a13f", 6, "v-02", 4)); |
| 63 | + CHECK_OK(fce_builder_put_u128(b, 7, 9, "v-9c", 4)); |
| 64 | + CHECK_OK(fce_builder_freeze(b)); |
| 65 | + CHECK_OK(fce_builder_close(b)); |
| 66 | + CHECK_OK(fce_reader_open_expected("ci_sorted_cache", &s, &r)); |
| 67 | + CHECK_OK(fce_reader_get(r, "k-a13f", 6, &v, &n)); |
| 68 | + CHECK(check_bytes(v, n, "v-02")); |
| 69 | + CHECK_OK(fce_reader_get_u128(r, 7, 9, &v, &n)); |
| 70 | + CHECK(check_bytes(v, n, "v-9c")); |
| 71 | + fce_reader_close(r); |
| 72 | +
|
| 73 | + s = fce_schema_default(); |
| 74 | + s.backend = FCE_BACKEND_DIRECT_TABLE; |
| 75 | + s.key_kind = FCE_KEY_U64; |
| 76 | + s.fixed_key_size = 8; |
| 77 | + s.user_flags = FCE_FLAG_ALLOW_SPARSE_DIRECT_TABLE; |
| 78 | + CHECK_OK(fce_builder_open("ci_direct_cache", &s, &b)); |
| 79 | + CHECK_OK(fce_builder_put_u64(b, 42, "v-d4", 4)); |
| 80 | + CHECK_OK(fce_builder_freeze(b)); |
| 81 | + CHECK_OK(fce_builder_close(b)); |
| 82 | + CHECK_OK(fce_reader_open_expected("ci_direct_cache", &s, &r)); |
| 83 | + CHECK_OK(fce_reader_get_u64(r, 42, &v, &n)); |
| 84 | + CHECK(check_bytes(v, n, "v-d4")); |
| 85 | + fce_reader_close(r); |
| 86 | +
|
| 87 | + s = fce_schema_default(); |
| 88 | + s.backend = FCE_BACKEND_RADIX; |
| 89 | + s.lookup = FCE_LOOKUP_PREFIX; |
| 90 | + CHECK_OK(fce_builder_open("ci_radix_cache", &s, &b)); |
| 91 | + CHECK_OK(fce_builder_put(b, "p-01a", 5, "x", 1)); |
| 92 | + CHECK_OK(fce_builder_put(b, "p-01b", 5, "y", 1)); |
| 93 | + CHECK_OK(fce_builder_put(b, "q-99z", 5, "z", 1)); |
| 94 | + CHECK_OK(fce_builder_freeze(b)); |
| 95 | + CHECK_OK(fce_builder_close(b)); |
| 96 | + CHECK_OK(fce_reader_open_expected("ci_radix_cache", &s, &r)); |
| 97 | + CHECK_OK(fce_reader_prefix_scan(r, "p-", 2, &it)); |
| 98 | + size_t hits = 0; |
| 99 | + const void *key_ptr = NULL; |
| 100 | + const void *value_ptr = NULL; |
| 101 | + size_t key_len = 0; |
| 102 | + size_t value_len = 0; |
| 103 | + while (fce_iterator_next(it, &key_ptr, &key_len, &value_ptr, &value_len) == FCE_OK) hits++; |
| 104 | + CHECK(hits == 2); |
| 105 | + fce_iterator_close(it); |
| 106 | + fce_reader_close(r); |
| 107 | +
|
| 108 | + s = fce_schema_default(); |
| 109 | + s.backend = FCE_BACKEND_MPH; |
| 110 | + CHECK_OK(fce_builder_open("ci_mph_cache", &s, &b)); |
| 111 | + CHECK_OK(fce_builder_put(b, "m-a7", 4, "x7", 2)); |
| 112 | + CHECK_OK(fce_builder_put(b, "m-b8", 4, "y8", 2)); |
| 113 | + CHECK_OK(fce_builder_freeze(b)); |
| 114 | + CHECK_OK(fce_builder_close(b)); |
| 115 | + CHECK_OK(fce_reader_open_expected("ci_mph_cache", &s, &r)); |
| 116 | + CHECK_OK(fce_reader_get(r, "m-b8", 4, &v, &n)); |
| 117 | + CHECK(check_bytes(v, n, "y8")); |
| 118 | + CHECK(fce_reader_get(r, "missing", 7, &v, &n) == FCE_ERR_NOT_FOUND); |
| 119 | + fce_reader_close(r); |
| 120 | +
|
| 121 | + s = fce_schema_default(); |
| 122 | + s.backend = FCE_BACKEND_LOG; |
| 123 | + s.allow_duplicate_put = 1; |
| 124 | + CHECK_OK(fce_builder_open("ci_log_cache", &s, &b)); |
| 125 | + CHECK_OK(fce_builder_put(b, "l-9x", 4, "v-a", 3)); |
| 126 | + CHECK_OK(fce_builder_freeze(b)); |
| 127 | + CHECK_OK(fce_builder_close(b)); |
| 128 | + CHECK_OK(fce_log_append("ci_log_cache", "l-9x", 4, "v-b", 3)); |
| 129 | + CHECK_OK(fce_reader_open_expected("ci_log_cache", &s, &r)); |
| 130 | + CHECK_OK(fce_reader_get(r, "l-9x", 4, &v, &n)); |
| 131 | + CHECK(check_bytes(v, n, "v-b")); |
| 132 | + fce_reader_close(r); |
| 133 | + CHECK_OK(fce_compact("ci_log_cache", FCE_BACKEND_SORTED_INDEX, "ci_compact_cache")); |
| 134 | +
|
| 135 | + int32_t values[] = {5, 5, 5, 9}; |
| 136 | + void *enc = NULL; |
| 137 | + void *dec = NULL; |
| 138 | + size_t enc_len = 0; |
| 139 | + size_t dec_len = 0; |
| 140 | + CHECK_OK(fce_codec_encode(FCE_CODEC_RLE, values, sizeof(values), &enc, &enc_len)); |
| 141 | + CHECK_OK(fce_codec_decode(FCE_CODEC_RLE, enc, enc_len, &dec, &dec_len)); |
| 142 | + CHECK(dec_len == sizeof(values) && memcmp(dec, values, sizeof(values)) == 0); |
| 143 | + fce_free(enc); |
| 144 | + fce_free(dec); |
| 145 | +
|
| 146 | + FceMemoryStats stats; |
| 147 | + CHECK_OK(fce_memory_stats(&stats)); |
| 148 | + CHECK(stats.active_allocations == 0); |
| 149 | + CHECK(stats.active_bytes == 0); |
| 150 | + return 0; |
| 151 | + } |
| 152 | + EOF |
| 153 | +
|
| 154 | + - name: Build and run C API smoke test |
| 155 | + shell: bash |
| 156 | + run: | |
| 157 | + cmake -S ci_smoke -B ci_smoke_build -DFCE_ENABLE_ZSTD=ON |
| 158 | + cmake --build ci_smoke_build --config Release --parallel 2 |
| 159 | + python - <<'PY' |
| 160 | + from pathlib import Path |
| 161 | + import subprocess |
| 162 | + candidates = [ |
| 163 | + Path("ci_smoke_build/ci_smoke"), |
| 164 | + Path("ci_smoke_build/Release/ci_smoke.exe"), |
| 165 | + Path("ci_smoke_build/Debug/ci_smoke.exe"), |
| 166 | + ] |
| 167 | + exe = next((p for p in candidates if p.exists()), None) |
| 168 | + if exe is None: |
| 169 | + raise SystemExit("ci_smoke executable not found") |
| 170 | + subprocess.run([str(exe)], check=True) |
| 171 | + PY |
| 172 | +
|
| 173 | + - name: CLI smoke test |
| 174 | + shell: bash |
| 175 | + run: | |
| 176 | + python - <<'PY' |
| 177 | + from pathlib import Path |
| 178 | + import json |
| 179 | + import shutil |
| 180 | + import struct |
| 181 | + import subprocess |
| 182 | + import tempfile |
| 183 | +
|
| 184 | + candidates = [ |
| 185 | + Path("build/fce"), |
| 186 | + Path("build/fce.exe"), |
| 187 | + Path("build/Release/fce.exe"), |
| 188 | + Path("build/Debug/fce.exe"), |
| 189 | + ] |
| 190 | + exe = next((p for p in candidates if p.exists()), None) |
| 191 | + if exe is None: |
| 192 | + raise SystemExit("fce executable not found") |
| 193 | +
|
| 194 | + root = Path(tempfile.mkdtemp(prefix="fce-cli-")) |
| 195 | + try: |
| 196 | + schema = root / "schema.json" |
| 197 | + records = root / "records.fce" |
| 198 | + cache = root / "cache" |
| 199 | + schema.write_text(json.dumps({ |
| 200 | + "backend": "radix", |
| 201 | + "lookup": "prefix", |
| 202 | + "key_kind": "bytes", |
| 203 | + "value_kind": "var_record", |
| 204 | + "allow_duplicate_put": True, |
| 205 | + }), encoding="utf-8") |
| 206 | + records.write_bytes(struct.pack("<QQ", 5, 3) + b"r-a7x" + b"v9q") |
| 207 | + subprocess.run([str(exe), "build", "--schema", str(schema), "--input", str(records), "--output", str(cache)], check=True) |
| 208 | + subprocess.run([str(exe), "validate", str(cache)], check=True) |
| 209 | + inspect = subprocess.run([str(exe), "inspect", str(cache)], check=True, text=True, stdout=subprocess.PIPE).stdout |
| 210 | + if "backend=radix" not in inspect: |
| 211 | + raise SystemExit(inspect) |
| 212 | + dump = subprocess.run([str(exe), "dump", str(cache), "--limit", "1"], check=True, text=True, stdout=subprocess.PIPE).stdout |
| 213 | + if "key_len=5" not in dump: |
| 214 | + raise SystemExit(dump) |
| 215 | + finally: |
| 216 | + shutil.rmtree(root, ignore_errors=True) |
| 217 | + PY |
| 218 | +
|
| 219 | + - name: Python binding smoke test |
| 220 | + shell: bash |
| 221 | + run: | |
| 222 | + python - <<'PY' |
| 223 | + from pathlib import Path |
| 224 | + import shutil |
| 225 | + import sys |
| 226 | + import tempfile |
| 227 | +
|
| 228 | + sys.path.insert(0, str(Path("python").resolve())) |
| 229 | + from fast_cache_engine import ( |
| 230 | + CacheBuilder, |
| 231 | + CacheReader, |
| 232 | + CacheSchema, |
| 233 | + export_sqlite, |
| 234 | + import_sqlite, |
| 235 | + version_string, |
| 236 | + ) |
| 237 | +
|
| 238 | + root = Path(tempfile.mkdtemp(prefix="fce-python-")) |
| 239 | + try: |
| 240 | + schema = CacheSchema(backend="sorted_index") |
| 241 | + cache = root / "cache" |
| 242 | + with CacheBuilder(cache, schema) as builder: |
| 243 | + builder.put(b"k-9f", b"v-2c") |
| 244 | + builder.freeze() |
| 245 | + with CacheReader(cache) as reader: |
| 246 | + assert bytes(reader.get(b"k-9f")) == b"v-2c" |
| 247 | +
|
| 248 | + db = root / "cache.sqlite" |
| 249 | + restored = root / "restored" |
| 250 | + export_sqlite(cache, db) |
| 251 | + import_sqlite(db, restored, schema) |
| 252 | + with CacheReader(restored) as reader: |
| 253 | + assert bytes(reader.get(b"k-9f")) == b"v-2c" |
| 254 | +
|
| 255 | + assert version_string() |
| 256 | + finally: |
| 257 | + shutil.rmtree(root, ignore_errors=True) |
| 258 | + PY |
0 commit comments