feat(db2-ephemeral): refuse a durable table without WO_DATA; WO_EPHEMERAL=1 opts out; .wob v8 table bit
- main.c, startup only: WO_DATA unset and a class carrying WO_CLASSF_TABLE
without WO_CLASSF_VOLATILE (`durable: true`, the default) refuses — exit 2,
one stderr line naming the class and the three ways forward (WO_DATA=<dir
or file>, WO_EPHEMERAL=1, @table(durable: false)); before, every write
was silently dropped at exit — the one outcome `durable: true` forbids
- WO_EPHEMERAL=1 (exact value) is the whole-program escape: one boot notice,
rc 0, the RAM path byte-for-byte the old one (db.c untouched); any other
value refuses; set alongside WO_DATA refuses regardless of tables; the
`resident: keys` loop still wins and is not rescued
- .wob v8: WO_CLASSF_TABLE 0x08 (WO_CLASSF_ALL 0x0f), set from emit.ml's
cr_is_table — the first cut keyed on !VOLATILE and refused every
class-bearing program (fibers' Tick, subprocess's ConnMsg), because v7
spelled `durable: true` as the mere absence of a bit
- loader refuses VOLATILE/RESIDENT_KEYS without the table bit ("storage
flags on a class that is not a @table"); a v7 image is refused by the
exact-match version check, as v7 refused v6; disasm prints `table`;
runner.ml's independent validator carries both rules; obj.h comment
- test_loader: test_storage_flags_need_table (forged flags word: both
refusals, and the same bits WITH the table bit load); no golden moved
- contract: docs/plan/oop-vm/00-wob-format.md "v8: the table bit"
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 863692a590d426da0047831ae315142ef5b24416)
This commit is contained in:
parent
0435bd96f5
commit
d38b4f864b
9 changed files with 180 additions and 18 deletions
|
|
@ -184,7 +184,7 @@ let dump (img : string) : string =
|
|||
opcodes 34-41). The disassembler tracks the emitter, not a range: an old
|
||||
image is a different format and reading it as this one would misrender. *)
|
||||
(* tracks emit.ml's wob_version and wob.h's WOB_VERSION *)
|
||||
if ver <> 7 then raise (Bad (Printf.sprintf "unsupported version %d" ver));
|
||||
if ver <> 8 then raise (Bad (Printf.sprintf "unsupported version %d" ver));
|
||||
let coff = u32 img 8 and ccnt = u32 img 12 in
|
||||
let koff = u32 img 16 and kcnt = u32 img 20 in
|
||||
let ioff = u32 img 24 and icnt = u32 img 28 in
|
||||
|
|
@ -264,6 +264,7 @@ let dump (img : string) : string =
|
|||
(if flags land 1 <> 0 then [ "gc" ] else [])
|
||||
@ (if flags land 2 <> 0 then [ "volatile" ] else [])
|
||||
@ (if flags land 4 <> 0 then [ "resident=keys" ] else [])
|
||||
@ (if flags land 8 <> 0 then [ "table" ] else [])
|
||||
in
|
||||
if parts = [] then "-" else String.concat "+" parts)
|
||||
(String.concat ", " fields))
|
||||
|
|
|
|||
|
|
@ -156,8 +156,9 @@ let wob_magic = 0x31424F57 (* "WOB1" read as an LE u32 *)
|
|||
builtins 70-83. v4 (iteration 7b): RC opcodes retired; gc mask = GC roots *)
|
||||
(* MUST track runtime/src/wob.h's WOB_VERSION — the loader is an exact-match
|
||||
check, so a drift here is not a warning, it is every image refused.
|
||||
v7 (databasev2 2): two class flag bits, no layout change. *)
|
||||
let wob_version = 7
|
||||
v7 (databasev2 2): two class flag bits, no layout change.
|
||||
v8 (databasev2 2 task 6a): the table bit, no layout change. *)
|
||||
let wob_version = 8
|
||||
|
||||
let wob_hdr_size = 44
|
||||
let wob_none = 0xFFFFFFFF
|
||||
|
|
@ -173,6 +174,9 @@ let classf_gc = 0x01
|
|||
(* databasev2 2: spare bits of the same flags word — see runtime/src/wob.h *)
|
||||
let classf_volatile = 0x02
|
||||
let classf_resident_keys = 0x04
|
||||
(* v8: has @table. The runtime's durability rules apply to these classes only;
|
||||
the two bits above are meaningful — and loader-accepted — only with it. *)
|
||||
let classf_table = 0x08
|
||||
|
||||
let op_nop = 0
|
||||
let op_loadk = 1
|
||||
|
|
@ -5093,7 +5097,8 @@ let emit ?(entry_ok : string -> bool = fun _ -> true) ~(syms : Types.symbols)
|
|||
Buf.u32 cls
|
||||
((if c.cr_gc then classf_gc else 0)
|
||||
lor (if c.cr_durable then 0 else classf_volatile)
|
||||
lor (if c.cr_resident_keys then classf_resident_keys else 0));
|
||||
lor (if c.cr_resident_keys then classf_resident_keys else 0)
|
||||
lor (if c.cr_is_table then classf_table else 0));
|
||||
Buf.u32 cls (Array.length c.cr_fields);
|
||||
Array.iter (fun (_, ty) -> Buf.u8 cls (field_kind p ty)) c.cr_fields;
|
||||
let pad = (4 - (Array.length c.cr_fields mod 4)) mod 4 in
|
||||
|
|
|
|||
|
|
@ -2402,7 +2402,7 @@ let validate_image (img : string) : string list =
|
|||
let u64 o = if ok 8 o then String.get_int64_le img o else 0L in
|
||||
let none = 0xFFFFFFFF in
|
||||
if u32 0 <> 0x31424F57 then fail "bad magic";
|
||||
if u32 4 <> 7 then fail "unsupported version"; (* v7: databasev2 2 *)
|
||||
if u32 4 <> 8 then fail "unsupported version"; (* v8: databasev2 2 task 6a *)
|
||||
let coff = u32 8 and ccnt = u32 12 in
|
||||
let koff = u32 16 and kcnt = u32 20 in
|
||||
let ioff = u32 24 and icnt = u32 28 in
|
||||
|
|
@ -2439,13 +2439,16 @@ let validate_image (img : string) : string list =
|
|||
let nm = u32 !o and flags = u32 (!o + 4) and fcnt = u32 (!o + 8) in
|
||||
o := !o + 12;
|
||||
if not (text_const nm) then fail (Printf.sprintf "class %d: bad name constant" i);
|
||||
(* v7 (databasev2 2): bit1 VOLATILE, bit2 RESIDENT_KEYS. This battery is a
|
||||
deliberately independent reimplementation of runtime/src/loader.c's
|
||||
validation, so it tracks the same contract — including refusing the pair
|
||||
that would leave rows neither logged nor resident. *)
|
||||
if flags land lnot 0x07 <> 0 then fail (Printf.sprintf "class %d: unknown flags" i);
|
||||
(* v7 (databasev2 2): bit1 VOLATILE, bit2 RESIDENT_KEYS; v8 (task 6a): bit3
|
||||
TABLE. This battery is a deliberately independent reimplementation of
|
||||
runtime/src/loader.c's validation, so it tracks the same contract —
|
||||
including refusing the pair that would leave rows neither logged nor
|
||||
resident, and storage bits on a class that is not a @table. *)
|
||||
if flags land lnot 0x0f <> 0 then fail (Printf.sprintf "class %d: unknown flags" i);
|
||||
if flags land 0x02 <> 0 && flags land 0x04 <> 0 then
|
||||
fail (Printf.sprintf "class %d: durable:false with resident:keys" i);
|
||||
if flags land 0x06 <> 0 && flags land 0x08 = 0 then
|
||||
fail (Printf.sprintf "class %d: storage flags on a class that is not a @table" i);
|
||||
if fcnt > 65535 then fail (Printf.sprintf "class %d: too many fields" i);
|
||||
class_fields.(i) <- fcnt;
|
||||
let kco = !o in (* the kind bytes' offset: the v3 index walk re-reads them *)
|
||||
|
|
|
|||
|
|
@ -11,11 +11,11 @@
|
|||
|
||||
All integers little-endian; offsets are absolute file offsets.
|
||||
|
||||
**Header (44 bytes):** magic `"WOB1"`, version 7 (databasev2 2; see "v7: table storage flags" below — v6 was iteration 36's "v6: the Int bitwise set", v5 iteration 19's "v5: Float and Bytes"), then offset/count u32 pairs for the constant pool, class table, interface section, and method table, then a u32 entry-method index (all-ones = none).
|
||||
**Header (44 bytes):** magic `"WOB1"`, version 8 (databasev2 2 task 6a; see "v8: the table bit" below — v7 was databasev2 2's "v7: table storage flags", v6 was iteration 36's "v6: the Int bitwise set", v5 iteration 19's "v5: Float and Bytes"), then offset/count u32 pairs for the constant pool, class table, interface section, and method table, then a u32 entry-method index (all-ones = none).
|
||||
|
||||
**Constant pool** — sequential entries: one tag byte; tag 0 = i64 follows; tag 1 = text (u32 length + bytes, no NUL); tag 2 = f64 as its IEEE 754 bit pattern in an LE u64 (v5). There is no Bytes tag: Bytes has no literal form.
|
||||
|
||||
**Class table** — per class: name constant index, flags u32 (bit0 = instances are `@gc`; **bit1 = `@table(durable: false)`, bit2 = `@table(resident: keys)`** — v7, and 0 in both means the pre-v7 behaviour of durable-and-fully-resident), field count, then one kind byte per field padded to a 4-byte boundary, then **three u32 arrays of per-field metadata** (v2), one entry per field each, in declaration order:
|
||||
**Class table** — per class: name constant index, flags u32 (bit0 = instances are `@gc`; **bit1 = `@table(durable: false)`, bit2 = `@table(resident: keys)`** — v7, and 0 in both means the pre-v7 behaviour of durable-and-fully-resident; **bit3 = the class has `@table`** — v8, required by bit1/bit2, clear on every class that is not a table), field count, then one kind byte per field padded to a 4-byte boundary, then **three u32 arrays of per-field metadata** (v2), one entry per field each, in declaration order:
|
||||
|
||||
1. `field_names[i]` — constant index of the field's name, or all-ones for "not recorded" (what a hand-built test image writes).
|
||||
2. `field_class[i]` — the class id the field refers to: its own class for an OWNED/GCREF field, its *element's* class for a container of records; `0xFFFFFFFE` marks a `json.Value` field, whose Text holds a raw JSON slice; `0xFFFFFFFD` a nullable scalar (`WO_NIL_SCALAR` nil); `0xFFFFFFFC` a plain `Bool` (json encodes `true`/`false`); `0xFFFFFFFB` a `?Bool` (both); `0xFFFFFFFA` a `?Float` (v5 — nil is `WO_NIL_FLOAT`, not `WO_NIL_SCALAR`); all-ones for none.
|
||||
|
|
@ -338,3 +338,29 @@ refuses this at compile time (WO-E102), and the loader refuses it again on the
|
|||
standing principle that what the loader accepts, the interpreter trusts. Both
|
||||
paths are gate-verified — the loader's by forging the flags word in an
|
||||
otherwise valid image, since `woc` will not emit one.
|
||||
|
||||
## v8: the table bit (databasev2 2 task 6a)
|
||||
|
||||
Again no layout change: one more spare bit of the class descriptor's `flags`
|
||||
u32.
|
||||
|
||||
**What v8 adds**
|
||||
|
||||
- `flags` bit3 — `WO_CLASSF_TABLE`: the class has `@table`; its instances are
|
||||
row ids and the runtime's durability rules apply to it. `woc` sets it from
|
||||
the class record's `cr_is_table`.
|
||||
- bit1 and bit2 now **require** bit3. A plain class, a variant class and a
|
||||
predeclared record (`Error`, `Stat`, …) have all three clear.
|
||||
|
||||
**Why.** `durable: true` is the default for a `@table`, and v7 spelled it as
|
||||
the *absence* of bit1 — which every non-table class also has. When the runtime
|
||||
started refusing a durable table without `WO_DATA` (task 6a) it had no way to
|
||||
tell `@table class Notes` from `class Tick`, and every class-bearing program
|
||||
refused. The bit is the missing fact, recorded where the other two are.
|
||||
|
||||
**Refused by the loader, independently of the compiler:** bit1 or bit2 set
|
||||
with bit3 clear ("storage flags on a class that is not a @table"). A v7 image
|
||||
is refused by the exact-match version check rather than read with bit3 clear —
|
||||
read that way it would contain no tables at all and silently skip every
|
||||
durability rule, the mirror image of the failure the v7 bump guarded against.
|
||||
Gate-verified by forging the flags word (`runtime/test/test_loader.c`).
|
||||
|
|
|
|||
|
|
@ -205,6 +205,16 @@ int wo_load_buf(wo_module *m, const uint8_t *buf, size_t len, char *err,
|
|||
if ((flags & WO_CLASSF_VOLATILE) && (flags & WO_CLASSF_RESIDENT_KEYS))
|
||||
BAIL("class %u: durable:false with resident:keys — rows would have "
|
||||
"nowhere to be read from", (unsigned)i);
|
||||
/* v8 (databasev2 2 task 6a): the storage bits describe a @table's
|
||||
* rows, so on a class without WO_CLASSF_TABLE they describe nothing —
|
||||
* refuse rather than let main.c's refusal loops see a table that is
|
||||
* not one. A v7 image (no table bit) is refused by the version check
|
||||
* above, the same way task 3's v7 refused v6: read with the bit clear
|
||||
* it would have no tables and silently skip every durability rule. */
|
||||
if ((flags & (WO_CLASSF_VOLATILE | WO_CLASSF_RESIDENT_KEYS)) &&
|
||||
!(flags & WO_CLASSF_TABLE))
|
||||
BAIL("class %u: storage flags on a class that is not a @table",
|
||||
(unsigned)i);
|
||||
if (fcnt > 65535) BAIL("class %u: too many fields", (unsigned)i);
|
||||
if (fcnt > k.len - k.off) BAIL("class %u: truncated kinds", (unsigned)i);
|
||||
for (uint32_t j = 0; j < fcnt; j++)
|
||||
|
|
|
|||
|
|
@ -243,10 +243,13 @@ int main(int argc, char **argv) {
|
|||
* serving program writes to sockets whose peers vanish. */
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
/* The database engine boots with the VM: every class IS a table.
|
||||
* Durability is opt-in — WO_DATA=<dir> opens <dir>/shard-0.wal,
|
||||
* Durability is the default — WO_DATA=<dir> opens <dir>/shard-0.wal,
|
||||
* replays it before the entry runs (boot-before-listeners doctrine),
|
||||
* and every insert commits before it acknowledges. Without WO_DATA
|
||||
* the engine runs RAM-only, which is what the corpus expects.
|
||||
* and every insert commits before it acknowledges. A program with any
|
||||
* durable @table (the default) refuses to start without WO_DATA;
|
||||
* WO_EPHEMERAL=1 opts into a RAM-only run (the corpus's mode), and
|
||||
* @table(durable: false) opts a table out. A class without @table is
|
||||
* storage for the engine but never a durable table (v8 WO_CLASSF_TABLE).
|
||||
* Arc stage 3 obligation: this whole block runs BEFORE the worker
|
||||
* shards spawn — replay completes before anything can serve, and the
|
||||
* engine's immutable class-table pointer is published to the worker
|
||||
|
|
@ -262,6 +265,16 @@ int main(int argc, char **argv) {
|
|||
VM.rt.db = &DB;
|
||||
DB.rt = &VM.rt; /* databasev2 2 (5c): the loop a borrow reads the WAL through */
|
||||
const char *data_dir = getenv("WO_DATA");
|
||||
const char *ephemeral = getenv("WO_EPHEMERAL");
|
||||
if (data_dir && data_dir[0] && ephemeral) {
|
||||
/* databasev2 2 (6a): the two knobs answer the same question with
|
||||
* opposite answers — refuse rather than pick one silently. */
|
||||
fprintf(stderr, "wovm: WO_EPHEMERAL=1 is incompatible with WO_DATA\n");
|
||||
wo_db_destroy(&DB);
|
||||
wo_vm_destroy(&VM);
|
||||
wo_module_free(&mod);
|
||||
return 2;
|
||||
}
|
||||
if (!data_dir || !data_dir[0]) {
|
||||
/* databasev2 3: the loader used to refuse `resident: keys` outright;
|
||||
* now it is accepted because UPDATE landed, but every row still
|
||||
|
|
@ -269,6 +282,9 @@ int main(int argc, char **argv) {
|
|||
* durable:false+resident:keys refusal does, rather than let reads
|
||||
* silently misbehave with no WAL to fold from. */
|
||||
for (uint32_t i = 0; i < mod.class_cnt; i++) {
|
||||
/* v8: the storage bits are a @table's; the loader refuses them
|
||||
* on anything else, so the table check here is belt and braces */
|
||||
if (!(mod.classes[i].flags & WO_CLASSF_TABLE)) continue;
|
||||
if (!(mod.classes[i].flags & WO_CLASSF_RESIDENT_KEYS)) continue;
|
||||
const char *cname = "?";
|
||||
int cnlen = 1;
|
||||
|
|
@ -286,6 +302,58 @@ int main(int argc, char **argv) {
|
|||
wo_module_free(&mod);
|
||||
return 2;
|
||||
}
|
||||
/* databasev2 2 (6a): a durable table (the default) without WO_DATA
|
||||
* used to run RAM-only and drop every write at exit — the one
|
||||
* outcome `durable: true` promises against. Refuse by name unless
|
||||
* the developer opted into RAM-only explicitly. Startup-only:
|
||||
* db.c's `w && table_is_durable` guards are untouched, so the RAM
|
||||
* path under WO_EPHEMERAL=1 is byte-for-byte the old one. After the
|
||||
* keys loop on purpose: a keys-resident table has nowhere to read
|
||||
* from at all, which is the more specific refusal and is not
|
||||
* rescued by WO_EPHEMERAL. `durable: true` is a @table property (v8
|
||||
* WO_CLASSF_TABLE): a plain class, variant or predeclared record is
|
||||
* not a table, so a program with no durable table starts as it
|
||||
* always did and WO_EPHEMERAL is not consulted at all. */
|
||||
uint32_t first_durable = mod.class_cnt;
|
||||
for (uint32_t i = 0; i < mod.class_cnt; i++) {
|
||||
if (!(mod.classes[i].flags & WO_CLASSF_TABLE)) continue;
|
||||
if (mod.classes[i].flags & WO_CLASSF_VOLATILE) continue;
|
||||
first_durable = i;
|
||||
break;
|
||||
}
|
||||
if (first_durable < mod.class_cnt && ephemeral && strcmp(ephemeral, "1") != 0) {
|
||||
fprintf(stderr,
|
||||
"wovm: WO_EPHEMERAL=%s is not accepted — the only accepted "
|
||||
"value is WO_EPHEMERAL=1.\n",
|
||||
ephemeral);
|
||||
wo_db_destroy(&DB);
|
||||
wo_vm_destroy(&VM);
|
||||
wo_module_free(&mod);
|
||||
return 2;
|
||||
}
|
||||
if (first_durable < mod.class_cnt && ephemeral) {
|
||||
fprintf(stderr,
|
||||
"wovm: WO_EPHEMERAL=1 — durable tables served from RAM, "
|
||||
"nothing is written.\n");
|
||||
} else if (first_durable < mod.class_cnt) {
|
||||
const char *cname = "?";
|
||||
int cnlen = 1;
|
||||
uint32_t k = mod.classes[first_durable].name;
|
||||
if (k < mod.const_cnt && mod.consts[k].s) {
|
||||
cname = mod.consts[k].s->data;
|
||||
cnlen = (int)mod.consts[k].s->len;
|
||||
}
|
||||
fprintf(stderr,
|
||||
"wovm: `%.*s` is a durable table (the default) and WO_DATA "
|
||||
"is not set — set WO_DATA=<dir or file> to keep its rows, "
|
||||
"WO_EPHEMERAL=1 to run RAM-only, or declare it "
|
||||
"@table(durable: false).\n",
|
||||
cnlen, cname);
|
||||
wo_db_destroy(&DB);
|
||||
wo_vm_destroy(&VM);
|
||||
wo_module_free(&mod);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
wo_schema compiled_schema;
|
||||
int have_schema = 0;
|
||||
|
|
|
|||
|
|
@ -63,7 +63,9 @@ typedef struct wo_rt {
|
|||
void *out; /* FILE*; kept void* so obj.h needn't pull in stdio */
|
||||
/* the database engine's handles (database/src), opaque here so the VM
|
||||
core needn't include engine headers: db = wo_db*, wal = wo_wal*.
|
||||
NULL = engine absent (test binaries) / durability off (no WO_DATA).
|
||||
NULL = engine absent (test binaries) / RAM-only under WO_EPHEMERAL=1
|
||||
(a program with any durable table — the default — refuses to start
|
||||
without WO_DATA; @table(durable: false) opts a table out).
|
||||
Set by main.c at boot; db.c casts. */
|
||||
void *db;
|
||||
void *wal;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,14 @@
|
|||
|
||||
/* ---- file header (44 bytes, absolute offsets) ---- */
|
||||
#define WOB_MAGIC 0x31424F57u /* "WOB1" read as LE u32 */
|
||||
#define WOB_VERSION 7u /* v7 (databasev2 2): two class flag bits —
|
||||
#define WOB_VERSION 8u /* v8 (databasev2 2 task 6a): one class flag bit —
|
||||
* WO_CLASSF_TABLE (the class has @table: a row-bearing class). No layout
|
||||
* change. Needed because `durable: true` is a property of @table classes
|
||||
* only, and v7 gave the runtime no way to tell a table from a plain class:
|
||||
* the no-WO_DATA refusal fired on every class-bearing program. A v7 image is
|
||||
* refused by the exact-match check, as v6 was by v7 (task 3): read with the
|
||||
* bit clear it would have no tables at all and silently skip every refusal.
|
||||
* v7 (databasev2 2): two class flag bits —
|
||||
* WO_CLASSF_VOLATILE (@table durable: false) and WO_CLASSF_RESIDENT_KEYS
|
||||
* (@table resident: keys). No layout change: both ride spare bits of the
|
||||
* class descriptor's existing `flags` u32, so the serialized shape is
|
||||
|
|
@ -655,7 +662,11 @@ typedef struct wo_classdesc {
|
|||
flags word means today's behaviour: durable, every row resident. */
|
||||
#define WO_CLASSF_VOLATILE 0x02u /* @table(durable: false) — never logged */
|
||||
#define WO_CLASSF_RESIDENT_KEYS 0x04u /* @table(resident: keys) — rows read from the log */
|
||||
#define WO_CLASSF_ALL 0x07u
|
||||
/* v8 (databasev2 2 task 6a): has @table — a row-bearing class. The two storage
|
||||
bits above require it; a plain class, variant or predeclared record has all
|
||||
three clear and is never a durable table. */
|
||||
#define WO_CLASSF_TABLE 0x08u
|
||||
#define WO_CLASSF_ALL 0x0fu
|
||||
|
||||
/* runtime object layout: 16-byte header then one 8-byte slot per field */
|
||||
static inline size_t wo_obj_size(const wo_classdesc *c) {
|
||||
|
|
|
|||
|
|
@ -109,8 +109,44 @@ static void test_rejects(void) {
|
|||
free(img);
|
||||
}
|
||||
|
||||
/* a valid one-class image whose class descriptor carries `flags` */
|
||||
static uint8_t *image_with_class_flags(uint32_t flags, size_t *len) {
|
||||
wb_t *b = wb_new();
|
||||
wb_const_int(b, 42);
|
||||
uint32_t kname = wb_const_text(b, "main");
|
||||
uint8_t kinds[] = {WO_K_SCALAR, WO_K_SCALAR};
|
||||
wb_class(b, kname, flags, kinds, 2);
|
||||
uint32_t code[] = {wo_ins_abc(WOP_RET, 0, 0, 0)};
|
||||
wb_method(b, kname, WOB_NONE, 0, 1, code, 1, NULL, 0, NULL, 0);
|
||||
return wb_finish(b, len);
|
||||
}
|
||||
|
||||
/* databasev2 2 (6a, .wob v8): the storage bits describe a @table's rows, so
|
||||
* they are meaningless — and refused — on a class without WO_CLASSF_TABLE.
|
||||
* woc never emits the combination; the loader refuses it independently. */
|
||||
static void test_storage_flags_need_table(void) {
|
||||
size_t len;
|
||||
uint8_t *img = image_with_class_flags(WO_CLASSF_VOLATILE, &len);
|
||||
expect_reject(img, len, "volatile without table");
|
||||
free(img);
|
||||
|
||||
img = image_with_class_flags(WO_CLASSF_RESIDENT_KEYS, &len);
|
||||
expect_reject(img, len, "resident:keys without table");
|
||||
free(img);
|
||||
|
||||
/* the same bits WITH the table bit load */
|
||||
img = image_with_class_flags(WO_CLASSF_TABLE | WO_CLASSF_VOLATILE, &len);
|
||||
wo_module m;
|
||||
char err[256] = "";
|
||||
T_EQ(wo_load_buf(&m, img, len, err, sizeof err), 0);
|
||||
T_EQ(m.classes[0].flags, WO_CLASSF_TABLE | WO_CLASSF_VOLATILE);
|
||||
wo_module_free(&m);
|
||||
free(img);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_happy_path();
|
||||
test_rejects();
|
||||
test_storage_flags_need_table();
|
||||
return t_report("test_loader");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue