Skip to content

Commit 0b48a07

Browse files
committed
test: guards for wrapped-lib soundness under non-trivial wrapping
Two forward-looking guards in [test/blackbox-tests/test-cases/ per-module-lib-deps/]: - [wrapped-transition-soundness.t]: a consumer reaches an inner module of a [(wrapped (transition ...))] library through the wrapper alias [Wrapped_lib.Inner_a.x]. Pins that the consumer rebuilds when the inner module's interface changes — its compile-rule deps must cover [wrapped_lib__Inner_a.cmi] (the mangled inner-module artifact, not the un-prefixed transition compat shim), not only the wrapper's [.cmi]. - [wrapped-from-vlib-soundness.t]: a consumer depends on a virtual-library implementation that inherits its [(wrapped ...)] setting from the vlib (the impl does not redeclare [wrapped]). Pins that the consumer rebuilds when a concrete vlib module's interface changes — its compile-rule deps must cover the impl's [.cmi] directory. Both properties hold trivially today via the cctx-wide compile-rule glob over each dep library's [.cmi] directory. The tests serve as forward-looking guards for changes that narrow compile-rule deps per-module: such changes must keep the inner-module / inherited- wrapped-library coverage they currently get for free. Test structure: [consumer] is a single-module lib named after itself (uses the main-module convention). jq regexes are anchored to the objdir ([\\.consumer\\.objs/byte/consumer\\.cm] and [consumer/\\.main\\.eobjs/byte/]) rather than relying on dune's internal mangling. Signed-off-by: Robin Bate Boerop <me@robinbb.com>
1 parent 824816b commit 0b48a07

2 files changed

Lines changed: 165 additions & 0 deletions

File tree

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
Regression guard for soundness when a consumer depends on an
2+
implementation that inherits its [(wrapped ...)] setting from the
3+
virtual library (the implementation does not redeclare [wrapped]).
4+
5+
[vlib] declares [(wrapped true)] with a virtual module [virt_iface]
6+
and a concrete sibling [helper]. [impl] implements [vlib] without
7+
redeclaring [wrapped]. The executable [main] depends on [impl] and
8+
reaches both modules via the vlib wrapper: [Vlib.Virt_iface.x] and
9+
[Vlib.Helper.h].
10+
11+
The implementation's closure includes both [virt_iface]'s impl and
12+
[vlib]'s concrete modules. [main]'s compile rule must therefore
13+
cover [impl]'s [.cmi] directory. Any future per-module narrowing
14+
that treats inherited-wrapped libraries as ordinary local libraries
15+
must still keep that coverage; otherwise a change to [helper]'s
16+
interface fails to invalidate [main].
17+
18+
$ cat > dune-project <<EOF
19+
> (lang dune 3.23)
20+
> EOF
21+
22+
$ mkdir vlib impl consumer
23+
24+
$ cat > vlib/dune <<EOF
25+
> (library
26+
> (name vlib)
27+
> (wrapped true)
28+
> (virtual_modules virt_iface))
29+
> EOF
30+
$ cat > vlib/virt_iface.mli <<EOF
31+
> val x : string
32+
> EOF
33+
$ cat > vlib/helper.ml <<EOF
34+
> let h = "h"
35+
> EOF
36+
$ cat > vlib/helper.mli <<EOF
37+
> val h : string
38+
> EOF
39+
40+
$ cat > impl/dune <<EOF
41+
> (library
42+
> (name impl)
43+
> (implements vlib))
44+
> EOF
45+
$ cat > impl/virt_iface.ml <<EOF
46+
> let x = "impl"
47+
> EOF
48+
49+
$ cat > consumer/dune <<EOF
50+
> (executable
51+
> (name main)
52+
> (libraries impl))
53+
> EOF
54+
$ cat > consumer/main.ml <<EOF
55+
> let () = print_string Vlib.Virt_iface.x; print_string Vlib.Helper.h
56+
> EOF
57+
58+
$ dune build @check
59+
60+
Edit [helper]'s interface (a concrete vlib module). [main] reaches
61+
[helper] through the vlib wrapper; the compile-rule deps must
62+
cover [vlib__Helper.cmi], so [main] rebuilds:
63+
64+
$ cat > vlib/helper.mli <<EOF
65+
> val h : string
66+
> val z : int
67+
> EOF
68+
$ cat > vlib/helper.ml <<EOF
69+
> let h = "h"
70+
> let z = 42
71+
> EOF
72+
$ dune build @check
73+
$ dune trace cat | jq -s 'include "dune"; [.[] | targetsMatchingFilter(test("consumer/\\.main\\.eobjs/byte/"))]'
74+
[
75+
{
76+
"target_files": [
77+
"_build/default/consumer/.main.eobjs/byte/dune__exe__Main.cmi",
78+
"_build/default/consumer/.main.eobjs/byte/dune__exe__Main.cmti"
79+
]
80+
},
81+
{
82+
"target_files": [
83+
"_build/default/consumer/.main.eobjs/byte/dune__exe__Main.cmo",
84+
"_build/default/consumer/.main.eobjs/byte/dune__exe__Main.cmt"
85+
]
86+
}
87+
]
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
Regression guard for wrapped-library soundness under
2+
[(wrapped (transition ...))].
3+
4+
[wrapped_lib] uses [(wrapped (transition ...))] with inner modules
5+
[inner_a] and [inner_b] plus a hand-written wrapper module that
6+
aliases both. The library [consumer] depends on [wrapped_lib] and
7+
writes [Wrapped_lib.Inner_a.x] — naming the wrapper and one inner
8+
module but not the other.
9+
10+
The wrapper's [.cmi] only carries an alias name; the type lives in
11+
the inner module's mangled artifact [wrapped_lib__Inner_a.cmi] (not
12+
the [inner_a.cmi] transition shim). So [consumer]'s compile rule
13+
must cover [wrapped_lib__Inner_a.cmi] alongside [wrapped_lib.cmi].
14+
Any future per-module narrowing of compile-rule deps must keep that
15+
coverage; otherwise a change to [inner_a]'s interface fails to
16+
invalidate [consumer].
17+
18+
$ cat > dune-project <<EOF
19+
> (lang dune 3.23)
20+
> EOF
21+
22+
$ cat > dune <<EOF
23+
> (library
24+
> (name wrapped_lib)
25+
> (wrapped (transition "use Wrapped_lib.X instead of X"))
26+
> (modules wrapped_lib inner_a inner_b))
27+
> (library
28+
> (name consumer)
29+
> (modules consumer)
30+
> (libraries wrapped_lib))
31+
> EOF
32+
33+
$ cat > wrapped_lib.ml <<EOF
34+
> module Inner_a = Inner_a
35+
> module Inner_b = Inner_b
36+
> EOF
37+
$ cat > inner_a.ml <<EOF
38+
> let x = "a"
39+
> EOF
40+
$ cat > inner_a.mli <<EOF
41+
> val x : string
42+
> EOF
43+
$ cat > inner_b.ml <<EOF
44+
> let y = "b"
45+
> EOF
46+
$ cat > inner_b.mli <<EOF
47+
> val y : string
48+
> EOF
49+
50+
$ cat > consumer.ml <<EOF
51+
> let _ = Wrapped_lib.Inner_a.x
52+
> EOF
53+
54+
$ dune build @check
55+
56+
Edit [inner_a]'s interface. [consumer] reaches [inner_a] through
57+
the wrapper [Wrapped_lib]; the compile-rule deps must cover
58+
[wrapped_lib__Inner_a.cmi], so [consumer] rebuilds:
59+
60+
$ cat > inner_a.mli <<EOF
61+
> val x : string
62+
> val z : int
63+
> EOF
64+
$ cat > inner_a.ml <<EOF
65+
> let x = "a"
66+
> let z = 42
67+
> EOF
68+
$ dune build @check
69+
$ dune trace cat | jq -s 'include "dune"; [.[] | targetsMatchingFilter(test("\\.consumer\\.objs/byte/consumer\\.cm"))]'
70+
[
71+
{
72+
"target_files": [
73+
"_build/default/.consumer.objs/byte/consumer.cmi",
74+
"_build/default/.consumer.objs/byte/consumer.cmo",
75+
"_build/default/.consumer.objs/byte/consumer.cmt"
76+
]
77+
}
78+
]

0 commit comments

Comments
 (0)