Skip to content

Commit 8bea914

Browse files
Add ‘dune promotion show’ command and tests (#12669)
Add 'dune promotion show' subcommand Implements a new command to display corrected file contents without applying the promotion. Users can now view what the promoted file would look like before deciding to promote. Fixes #3883 --------- Signed-off-by: Maxence Maury <[email protected]>
1 parent 530abf0 commit 8bea914

File tree

4 files changed

+299
-1
lines changed

4 files changed

+299
-1
lines changed

bin/promotion.ml

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,24 @@ let display_files files_to_promote =
3030
Console.printf "%s" (Diff_promotion.File.source file |> Path.Source.to_string))
3131
;;
3232

33+
let show_corrected_contents files_to_promote =
34+
let open Fiber.O in
35+
let files = Diff_promotion.load_db () |> Diff_promotion.filter_db files_to_promote in
36+
let+ () = Fiber.return () in
37+
List.iter files ~f:(fun file ->
38+
let correction_file = Diff_promotion.File.correction_file file in
39+
if Path.exists correction_file
40+
then (
41+
let contents = Io.read_file correction_file in
42+
Console.printf "%s" contents)
43+
else
44+
User_warning.emit
45+
[ Pp.textf
46+
"Corrected file does not exist for %s."
47+
(Diff_promotion.File.source file |> Path.Source.to_string_maybe_quoted)
48+
])
49+
;;
50+
3351
module Apply = struct
3452
let info =
3553
let doc = "Promote files from the last run" in
@@ -116,11 +134,28 @@ module Files = struct
116134
let command = Cmd.v info term
117135
end
118136

137+
module Show = struct
138+
let info = Cmd.info ~doc:"Display contents of a corrected file" "show"
139+
140+
let term =
141+
let+ builder = Common.Builder.term
142+
and+ files =
143+
Arg.(value & pos_all Cmdliner.Arg.file [] & info [] ~docv:"FILE" ~doc:None)
144+
in
145+
let common, config = Common.init builder in
146+
let files_to_promote = files_to_promote ~common files in
147+
Scheduler.go_with_rpc_server ~common ~config (fun () ->
148+
show_corrected_contents files_to_promote)
149+
;;
150+
151+
let command = Cmd.v info term
152+
end
153+
119154
let info =
120155
Cmd.info ~doc:"Control how changes are propagated back to source code." "promotion"
121156
;;
122157

123-
let group = Cmd.group info [ Files.command; Apply.command; Diff.command ]
158+
let group = Cmd.group info [ Files.command; Apply.command; Diff.command; Show.command ]
124159

125160
let promote =
126161
command_alias ~orig_name:"promotion apply" Apply.command Apply.term "promote"

doc/changes/added/12669.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
- Introduce `dune promotion show` command to display the contents of corrected
2+
files that are ready for promotion. This allows users to preview changes
3+
before running `dune promote`. The command accepts file arguments to show
4+
specific files, or displays all promotable files when called without
5+
arguments. (#12669, fixes #3883, @MixiMaxiMouse)

src/promote/diff_promotion.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module File : sig
77
val in_staging_area : Path.Source.t -> Path.Build.t
88
val compare : t -> t -> ordering
99
val source : t -> Path.Source.t
10+
val correction_file : t -> Path.t
1011

1112
(** Register an intermediate file to promote. The build path may point to the
1213
sandbox and the file will be moved to the staging area. *)
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
Test the 'dune promotion show' command
2+
3+
=== WHAT THIS TEST VERIFIES ===
4+
This test ensures that 'dune promotion show' correctly displays the contents
5+
of corrected files that are ready for promotion, without modifying the source
6+
files. The command allows users to preview what changes would be applied before
7+
running 'dune promote'.
8+
9+
Key properties tested:
10+
1. Shows corrected content for files with failed diff rules
11+
2. Is read-only (never modifies source files)
12+
3. Handles single file, multiple files, and all files (no args)
13+
4. Proper warning handling for non-promotable files
14+
5. Files with 'diff' (not 'diff?') are promotable
15+
16+
=== WHY THIS MATTERS ===
17+
The 'show' command is critical for user workflow: it lets developers review
18+
auto-generated corrections before accepting them. This is especially important
19+
for generated code, test expectations, or formatted output where users need to
20+
verify the changes make sense before committing them.
21+
22+
=== HOW TO VERIFY CORRECTNESS ===
23+
1. Before 'runtest': show should warn (no corrections available)
24+
2. After 'runtest': show should display corrected content from _build
25+
3. After 'show': source files should remain unchanged (read-only guarantee)
26+
4. Output includes trailing newline after each file's content
27+
5. Only 'diff' rules create promotions; 'diff?' rules may not always generate them
28+
29+
Setup a basic dune project with promotion-capable rules
30+
31+
We create different promotion scenarios to test various behaviors:
32+
- a.expected: uses 'diff' (creates promotion when files differ)
33+
- b.expected: uses 'diff?' in progn (creates promotion when files differ)
34+
- c.expected: uses 'diff?' in separate action (may not create promotion)
35+
36+
$ cat > dune-project << EOF
37+
> (lang dune 2.0)
38+
> EOF
39+
40+
$ cat > dune << EOF
41+
> (rule
42+
> (alias runtest)
43+
> (action
44+
> (diff a.expected a.actual)))
45+
>
46+
> (rule
47+
> (with-stdout-to a.actual
48+
> (echo "A actual\n")))
49+
>
50+
> (rule
51+
> (alias runtest)
52+
> (action
53+
> (progn
54+
> (with-stdout-to b.actual
55+
> (echo "B actual\n"))
56+
> (diff? b.expected b.actual))))
57+
>
58+
> (rule
59+
> (with-stdout-to c.actual
60+
> (echo "C actual\n")))
61+
>
62+
> (rule
63+
> (alias runtest)
64+
> (action
65+
> (diff? c.expected c.actual)))
66+
> EOF
67+
68+
$ echo 'A expected' > a.expected
69+
$ echo 'B expected' > b.expected
70+
$ echo 'C expected' > c.expected
71+
72+
=== TEST: Before tests run, no promotions available ===
73+
When no tests have run yet, there are no corrected files in _build,
74+
so 'show' should return a warning (not an error, as this is informational).
75+
76+
$ dune promotion show a.expected 2>&1
77+
Warning: Nothing to promote for a.expected.
78+
79+
=== TEST: Generate corrections by running tests ===
80+
Running tests will create .actual files in _build that differ from
81+
the .expected files. Note that only 'diff' and some 'diff?' rules
82+
actually create promotable files.
83+
84+
$ dune runtest 2>&1
85+
File "a.expected", line 1, characters 0-0:
86+
Error: Files _build/default/a.expected and _build/default/a.actual differ.
87+
File "b.expected", line 1, characters 0-0:
88+
Error: Files _build/default/b.expected and _build/default/b.actual differ.
89+
[1]
90+
91+
=== TEST: Show corrected contents of a single file ===
92+
After tests run, 'show' should display the contents of the corrected
93+
file from _build without modifying the source file.
94+
Note: Output includes a trailing newline after the content.
95+
96+
$ dune promotion show a.expected
97+
A actual
98+
99+
100+
=== TEST: Show another single file ===
101+
Verify 'show' works consistently for different files.
102+
103+
$ dune promotion show b.expected
104+
B actual
105+
106+
107+
=== TEST: Show multiple files at once ===
108+
The command should accept multiple file arguments and display their
109+
corrected contents in the order specified.
110+
Each file's content is followed by a newline.
111+
112+
$ dune promotion show a.expected b.expected
113+
A actual
114+
115+
B actual
116+
117+
118+
=== TEST: Mixed valid and invalid arguments ===
119+
When some arguments are promotable and others are not, the command should:
120+
- Show warnings for invalid files (appears before content)
121+
- Continue processing valid files
122+
- Display content for all valid files
123+
124+
This is important for scripting and batch operations where partial
125+
success is better than complete failure.
126+
127+
$ touch nothing-to-promote.txt
128+
$ dune promotion show a.expected nothing-to-promote.txt b.expected 2>&1
129+
Warning: Nothing to promote for nothing-to-promote.txt.
130+
A actual
131+
132+
B actual
133+
134+
135+
=== TEST: Non-existent file handling ===
136+
When a specified file doesn't exist at all (not just non-promotable),
137+
the command fails with a usage error and exit code 1.
138+
139+
This is different from "nothing to promote" - the file must exist
140+
in the filesystem to be checked for promotions.
141+
142+
$ dune promotion show does-not-exist.ml 2>&1
143+
dune: FILE… arguments: no 'does-not-exist.ml' file or directory
144+
Usage: dune promotion show [OPTION]… [FILE]…
145+
Try 'dune promotion show --help' or 'dune --help' for more information.
146+
[1]
147+
148+
=== TEST: Show all corrected files (no arguments) ===
149+
When called without arguments, 'show' should display all files that
150+
have corrections available. The order may vary.
151+
152+
$ dune promotion show
153+
B actual
154+
155+
A actual
156+
157+
158+
=== TEST: Verify read-only behavior ===
159+
Critical property: 'show' must never modify source files.
160+
After all the 'show' operations above, source files should still
161+
contain their original "expected" content, not the "actual" content.
162+
163+
$ cat a.expected
164+
A expected
165+
$ cat b.expected
166+
B expected
167+
$ cat c.expected
168+
C expected
169+
170+
=== TEST: Files with diff? may not always create promotions ===
171+
The c.expected file uses 'diff?' in a separate action, which doesn't
172+
always create a promotion entry. This is expected behavior.
173+
174+
$ dune promotion show c.expected > c.shown
175+
Warning: Nothing to promote for c.expected.
176+
$ dune promote c.expected 2>&1
177+
Warning: Nothing to promote for c.expected.
178+
179+
Since there was nothing to promote, c.expected remains unchanged
180+
and the diff shows they're different (original vs unchanged).
181+
182+
$ diff c.shown c.expected
183+
0a1
184+
> C expected
185+
[1]
186+
187+
=== TEST: After showing, files remain available ===
188+
The 'show' command is read-only, so files remain available for
189+
promotion after being shown.
190+
191+
$ dune promotion show c.expected 2>&1
192+
Warning: Nothing to promote for c.expected.
193+
194+
=== TEST: Other files still available ===
195+
Files that haven't been promoted remain available.
196+
197+
$ dune promotion show a.expected
198+
A actual
199+
200+
201+
=== EDGE CASE: Empty corrected file ===
202+
Empty files are a valid edge case - a diff might result in deleting
203+
all content. However, 'diff?' may not create a promotion for such files.
204+
205+
This tests robustness against zero-length file content.
206+
207+
$ cat > dune << EOF
208+
> (rule
209+
> (alias runtest)
210+
> (action
211+
> (diff? empty.expected empty.actual)))
212+
>
213+
> (rule
214+
> (with-stdout-to empty.actual
215+
> (progn)))
216+
> EOF
217+
218+
$ echo 'not empty' > empty.expected
219+
$ dune runtest 2>&1
220+
221+
In this case, 'diff?' doesn't create a promotion entry for the empty file.
222+
223+
$ dune promotion show empty.expected
224+
Warning: Nothing to promote for empty.expected.
225+
226+
=== EDGE CASE: Multiline content ===
227+
Real-world promoted files often contain multiple lines. However, we need
228+
to verify whether 'diff?' creates promotions for such files.
229+
230+
$ cat > dune << EOF
231+
> (rule
232+
> (alias runtest)
233+
> (action
234+
> (diff? multi.expected multi.actual)))
235+
>
236+
> (rule
237+
> (with-stdout-to multi.actual
238+
> (progn
239+
> (echo "line 1\n")
240+
> (echo "line 2\n")
241+
> (echo "line 3\n"))))
242+
> EOF
243+
244+
$ echo 'old content' > multi.expected
245+
$ dune runtest 2>&1
246+
247+
This 'diff?' rule also doesn't create a promotion entry.
248+
249+
$ dune promotion show multi.expected
250+
Warning: Nothing to promote for multi.expected.
251+
252+
=== TEST: Multiple non-promotable files ===
253+
When multiple files have no promotions available, each gets a warning.
254+
255+
$ dune promotion show multi.expected empty.expected
256+
Warning: Nothing to promote for multi.expected.
257+
Warning: Nothing to promote for empty.expected.

0 commit comments

Comments
 (0)