max / quasi
1 file changed,
+109 insertions,
-0 deletions
| @@ -20,6 +20,12 @@ | |||
| 20 | 20 | python3 scripts/population.py # the count, per tree | |
| 21 | 21 | python3 scripts/population.py --list # one `path:line name -> type` per line | |
| 22 | 22 | python3 scripts/population.py --json # the same, machine-readable | |
| 23 | + | python3 scripts/population.py --selftest # the predicate against known answers | |
| 24 | + | ||
| 25 | + | Run `--selftest` before quoting a number from this. Every case in it is a defect | |
| 26 | + | that actually happened: a `#[cfg(test)]` stripper that brace-matched from the | |
| 27 | + | next `{` blanked four real functions in goingson, a doc-comment mention of a | |
| 28 | + | vocabulary type was counted as a construction, and a tuple return was missed. | |
| 23 | 29 | ||
| 24 | 30 | Paths are read relative to `~/Code` by default; pass `--root` for a tree | |
| 25 | 31 | somewhere else. | |
| @@ -343,17 +349,120 @@ | |||
| 343 | 349 | return found | |
| 344 | 350 | ||
| 345 | 351 | ||
| 352 | + | # The cases the implementation has been wrong on, as executable assertions. | |
| 353 | + | # Every one is a defect that actually happened: a naive `#[cfg(test)]` stripper | |
| 354 | + | # ate four real functions, a doc-comment mention was counted as a construction, | |
| 355 | + | # and a tuple return was missed. `--selftest` is how those stay fixed. | |
| 356 | + | SELFTEST = [ | |
| 357 | + | # (source, expected [(fn, head)]) | |
| 358 | + | # A plain shape. | |
| 359 | + | ("use quasi_router::Node;\nfn body(cx: &Cx) -> Node { Node::page(\"x\") }", | |
| 360 | + | [("body", "Node")]), | |
| 361 | + | # Wrappers are stripped outside-in; `Result`'s payload is its first argument. | |
| 362 | + | ("use quasi_router::{Node, Row};\n" | |
| 363 | + | "fn a(c: &C) -> Result<Vec<Node>, E> { todo!() }\n" | |
| 364 | + | "fn b(c: &C) -> Option<Row> { todo!() }", | |
| 365 | + | [("a", "Node"), ("b", "Row")]), | |
| 366 | + | # `Result<(), E>` names no vocabulary type and is not a shape. | |
| 367 | + | ("use quasi_router::Node;\nfn a(c: &C) -> Result<(), E> { todo!() }", []), | |
| 368 | + | # A doc comment naming a vocabulary type is not a construction. This is the | |
| 369 | + | # defect behind two of the five irreproducible counts. | |
| 370 | + | ("use quasi_router::Node;\n/// Returns a Node, eventually.\nfn a(c: &C) -> u32 { 0 }", | |
| 371 | + | []), | |
| 372 | + | # Nor is a string literal. | |
| 373 | + | ("use quasi_router::Node;\nfn a(c: &C) -> u32 { let s = \"fn x() -> Node\"; 0 }", | |
| 374 | + | []), | |
| 375 | + | # An inline `#[cfg(test)]` block is stripped ... | |
| 376 | + | ("use quasi_router::Node;\n" | |
| 377 | + | "fn real(c: &C) -> Node { todo!() }\n" | |
| 378 | + | "#[cfg(test)]\nmod tests {\n fn fake(c: &C) -> Node { todo!() }\n}\n", | |
| 379 | + | [("real", "Node")]), | |
| 380 | + | # ... but `#[cfg(test)] mod tests;` has no block, and brace-matching from | |
| 381 | + | # the next `{` would blank an arbitrary later region. Four real goingson | |
| 382 | + | # functions were lost to exactly this. | |
| 383 | + | ("use quasi_router::Node;\n" | |
| 384 | + | "#[cfg(test)]\nmod tests;\n" | |
| 385 | + | "fn survives(c: &C) -> Node { todo!() }\n", | |
| 386 | + | [("survives", "Node")]), | |
| 387 | + | # A signature with no body is a trait method or an extern, not a shape. | |
| 388 | + | ("use quasi_router::Node;\ntrait T { fn a(&self) -> Node; }", []), | |
| 389 | + | # A returned unit is not a shape. | |
| 390 | + | ("use quasi_router::Node;\nfn a(c: &C) {}", []), | |
| 391 | + | # A generic parameter list before the arguments must not break the parse. | |
| 392 | + | ("use quasi_router::Node;\nfn a<T: Into<String>>(t: T) -> Node { todo!() }", | |
| 393 | + | [("a", "Node")]), | |
| 394 | + | # A `where` clause sits between the return type and the body. | |
| 395 | + | ("use quasi_router::Node;\nfn a<T>(t: T) -> Node where T: Into<String> { todo!() }", | |
| 396 | + | [("a", "Node")]), | |
| 397 | + | # A tuple return: one site in the trees, and `shaped` names one type, but it | |
| 398 | + | # is a shape function and it counts. | |
| 399 | + | ("use quasi_router::Cells;\n" | |
| 400 | + | "fn a(c: &C) -> (Vec<&'static str>, Vec<Cells>) { todo!() }", | |
| 401 | + | [("a", "Cells")]), | |
| 402 | + | # A file-local type wearing a vocabulary name shadows it. | |
| 403 | + | ("use quasi_router::Node;\nstruct Row;\nfn a(c: &C) -> Row { Row }", []), | |
| 404 | + | # Reached by path rather than by import. | |
| 405 | + | ("fn a(c: &C) -> quasi_router::Node { todo!() }", [("a", "Node")]), | |
| 406 | + | # A vocabulary name that was never imported and is not path-qualified is | |
| 407 | + | # somebody else's type. | |
| 408 | + | ("fn a(c: &C) -> Node { todo!() }", []), | |
| 409 | + | # An alias is resolved for MEMBERSHIP and reported AS WRITTEN. Both halves | |
| 410 | + | # are deliberate: the shape counts, and `--list` says what the source says, | |
| 411 | + | # which is why the record's own return-type histogram lists "Described | |
| 412 | + | # (Screen alias) 18" as its own row rather than folding it into Screen. | |
| 413 | + | ("use quasi_router::Screen as Described;\nfn a(c: &C) -> Described { todo!() }", | |
| 414 | + | [("a", "Described")]), | |
| 415 | + | # `Response` is quasi's transport, not its description vocabulary. 566 | |
| 416 | + | # further functions in these directories return one; admitting them roughly | |
| 417 | + | # doubles the count. | |
| 418 | + | ("use quasi_router::Response;\nfn a(c: &C) -> Response { todo!() }", []), | |
| 419 | + | ] | |
| 420 | + | ||
| 421 | + | ||
| 422 | + | def selftest() -> int: | |
| 423 | + | """Run the predicate over synthetic sources with known answers.""" | |
| 424 | + | import tempfile | |
| 425 | + | ||
| 426 | + | failures = 0 | |
| 427 | + | for n, (src, expected) in enumerate(SELFTEST, 1): | |
| 428 | + | with tempfile.TemporaryDirectory() as d: | |
| 429 | + | f = Path(d) / "case.rs" | |
| 430 | + | f.write_text(src, encoding="utf-8") | |
| 431 | + | got = [(h["fn"], head_type(h["returns"]) or "?") for h in shapes_in(f)] | |
| 432 | + | # head_type is re-applied for the label only; membership already | |
| 433 | + | # filtered. Compare as sets, since order is not the claim. | |
| 434 | + | want = {(a, b) for a, b in expected} | |
| 435 | + | have = {(a, b) for a, b in got} | |
| 436 | + | if have != want: | |
| 437 | + | failures += 1 | |
| 438 | + | print(f"case {n} FAILED") | |
| 439 | + | print(f" source: {src!r}") | |
| 440 | + | print(f" expected: {sorted(want)}") | |
| 441 | + | print(f" got: {sorted(have)}") | |
| 442 | + | total = len(SELFTEST) | |
| 443 | + | if failures: | |
| 444 | + | print(f"\n{failures} of {total} cases failed.") | |
| 445 | + | return 1 | |
| 446 | + | print(f"{total} of {total} cases pass.") | |
| 447 | + | return 0 | |
| 448 | + | ||
| 449 | + | ||
| 346 | 450 | def main() -> int: | |
| 347 | 451 | ap = argparse.ArgumentParser(description=__doc__, | |
| 348 | 452 | formatter_class=argparse.RawDescriptionHelpFormatter) | |
| 349 | 453 | ap.add_argument("--root", default=str(Path.home() / "Code")) | |
| 350 | 454 | ap.add_argument("--list", action="store_true") | |
| 351 | 455 | ap.add_argument("--json", action="store_true") | |
| 456 | + | ap.add_argument("--selftest", action="store_true", | |
| 457 | + | help="run the predicate over synthetic sources with known answers") | |
| 352 | 458 | ap.add_argument("--wide", action="store_true", | |
| 353 | 459 | help="also count the deferred leaf suppliers (Rest, Candidate, " | |
| 354 | 460 | "Accepted, RegionKind, Chrome)") | |
| 355 | 461 | args = ap.parse_args() | |
| 356 | 462 | ||
| 463 | + | if args.selftest: | |
| 464 | + | return selftest() | |
| 465 | + | ||
| 357 | 466 | if args.wide: | |
| 358 | 467 | VOCABULARY.update(DEFERRED_LEAVES) | |
| 359 | 468 |