A fork of mtelver's day10 project
1(** Unit tests for Day10_lib.Atomic_swap module.
2
3 These tests verify the atomic swap mechanism for graceful degradation:
4 - Successful swaps replace old docs with new
5 - Failed builds preserve existing docs
6 - Interrupted swaps are recovered on startup *)
7
8let test_dir = ref ""
9
10let setup () =
11 let dir = Filename.temp_dir "test-atomic-swap-" "" in
12 test_dir := dir;
13 (* Create html/p structure *)
14 let html_dir = Filename.concat dir "html" in
15 let p_dir = Filename.concat html_dir "p" in
16 Unix.mkdir html_dir 0o755;
17 Unix.mkdir p_dir 0o755;
18 html_dir
19
20let teardown () =
21 if !test_dir <> "" then begin
22 ignore (Sys.command (Printf.sprintf "rm -rf %s" !test_dir));
23 test_dir := ""
24 end
25
26let write_file path content =
27 let oc = open_out path in
28 output_string oc content;
29 close_out oc
30
31let read_file path =
32 let ic = open_in path in
33 let content = really_input_string ic (in_channel_length ic) in
34 close_in ic;
35 content
36
37let file_exists path = Sys.file_exists path
38
39(** Test: cleanup_stale_dirs removes .new directories *)
40let test_cleanup_stale_new () =
41 let html_dir = setup () in
42 let pkg_dir = Filename.concat (Filename.concat html_dir "p") "test-pkg" in
43 Unix.mkdir pkg_dir 0o755;
44 let stale_new = Filename.concat pkg_dir "1.0.0.new" in
45 Unix.mkdir stale_new 0o755;
46 write_file (Filename.concat stale_new "index.html") "stale content";
47
48 assert (file_exists stale_new);
49 Day10_lib.Atomic_swap.cleanup_stale_dirs ~html_dir;
50 assert (not (file_exists stale_new));
51
52 teardown ();
53 Printf.printf "PASS: test_cleanup_stale_new\n%!"
54
55(** Test: cleanup_stale_dirs removes .old directories *)
56let test_cleanup_stale_old () =
57 let html_dir = setup () in
58 let pkg_dir = Filename.concat (Filename.concat html_dir "p") "test-pkg" in
59 Unix.mkdir pkg_dir 0o755;
60 let stale_old = Filename.concat pkg_dir "1.0.0.old" in
61 Unix.mkdir stale_old 0o755;
62 write_file (Filename.concat stale_old "index.html") "stale old content";
63
64 assert (file_exists stale_old);
65 Day10_lib.Atomic_swap.cleanup_stale_dirs ~html_dir;
66 assert (not (file_exists stale_old));
67
68 teardown ();
69 Printf.printf "PASS: test_cleanup_stale_old\n%!"
70
71(** Test: cleanup_stale_dirs preserves normal directories *)
72let test_cleanup_preserves_normal () =
73 let html_dir = setup () in
74 let pkg_dir = Filename.concat (Filename.concat html_dir "p") "test-pkg" in
75 Unix.mkdir pkg_dir 0o755;
76 let normal_dir = Filename.concat pkg_dir "1.0.0" in
77 Unix.mkdir normal_dir 0o755;
78 write_file (Filename.concat normal_dir "index.html") "good content";
79
80 Day10_lib.Atomic_swap.cleanup_stale_dirs ~html_dir;
81 assert (file_exists normal_dir);
82 assert (read_file (Filename.concat normal_dir "index.html") = "good content");
83
84 teardown ();
85 Printf.printf "PASS: test_cleanup_preserves_normal\n%!"
86
87(** Test: get_swap_paths returns correct paths for blessed packages *)
88let test_swap_paths_blessed () =
89 let html_dir = "/test/html" in
90 let staging, final, old = Day10_lib.Atomic_swap.get_swap_paths
91 ~html_dir ~pkg:"my-pkg" ~version:"2.0.0" ~blessed:true ~universe:"ignored" in
92 assert (staging = "/test/html/p/my-pkg/2.0.0.new");
93 assert (final = "/test/html/p/my-pkg/2.0.0");
94 assert (old = "/test/html/p/my-pkg/2.0.0.old");
95 Printf.printf "PASS: test_swap_paths_blessed\n%!"
96
97(** Test: get_swap_paths returns correct paths for universe packages *)
98let test_swap_paths_universe () =
99 let html_dir = "/test/html" in
100 let staging, final, old = Day10_lib.Atomic_swap.get_swap_paths
101 ~html_dir ~pkg:"my-pkg" ~version:"2.0.0" ~blessed:false ~universe:"abc123" in
102 assert (staging = "/test/html/u/abc123/my-pkg/2.0.0.new");
103 assert (final = "/test/html/u/abc123/my-pkg/2.0.0");
104 assert (old = "/test/html/u/abc123/my-pkg/2.0.0.old");
105 Printf.printf "PASS: test_swap_paths_universe\n%!"
106
107(** Test: commit swaps staging to final *)
108let test_commit_success () =
109 let html_dir = setup () in
110 let pkg_dir = Filename.concat (Filename.concat html_dir "p") "test-pkg" in
111 Unix.mkdir pkg_dir 0o755;
112
113 (* Create staging directory with new content *)
114 let staging_dir = Filename.concat pkg_dir "1.0.0.new" in
115 Unix.mkdir staging_dir 0o755;
116 write_file (Filename.concat staging_dir "index.html") "new content";
117
118 let result = Day10_lib.Atomic_swap.commit ~html_dir ~pkg:"test-pkg" ~version:"1.0.0" ~blessed:true ~universe:"" in
119 assert result;
120
121 (* Verify staging was moved to final *)
122 let final_dir = Filename.concat pkg_dir "1.0.0" in
123 assert (file_exists final_dir);
124 assert (read_file (Filename.concat final_dir "index.html") = "new content");
125 assert (not (file_exists staging_dir));
126
127 teardown ();
128 Printf.printf "PASS: test_commit_success\n%!"
129
130(** Test: commit replaces existing with atomic swap *)
131let test_commit_replaces_existing () =
132 let html_dir = setup () in
133 let pkg_dir = Filename.concat (Filename.concat html_dir "p") "test-pkg" in
134 Unix.mkdir pkg_dir 0o755;
135
136 (* Create existing final directory with old content *)
137 let final_dir = Filename.concat pkg_dir "1.0.0" in
138 Unix.mkdir final_dir 0o755;
139 write_file (Filename.concat final_dir "index.html") "old content";
140
141 (* Create staging directory with new content *)
142 let staging_dir = Filename.concat pkg_dir "1.0.0.new" in
143 Unix.mkdir staging_dir 0o755;
144 write_file (Filename.concat staging_dir "index.html") "new content";
145
146 let result = Day10_lib.Atomic_swap.commit ~html_dir ~pkg:"test-pkg" ~version:"1.0.0" ~blessed:true ~universe:"" in
147 assert result;
148
149 (* Verify old was replaced with new *)
150 assert (read_file (Filename.concat final_dir "index.html") = "new content");
151 assert (not (file_exists staging_dir));
152 (* .old should be cleaned up *)
153 assert (not (file_exists (Filename.concat pkg_dir "1.0.0.old")));
154
155 teardown ();
156 Printf.printf "PASS: test_commit_replaces_existing\n%!"
157
158(** Test: rollback removes staging, preserves existing *)
159let test_rollback_preserves_existing () =
160 let html_dir = setup () in
161 let pkg_dir = Filename.concat (Filename.concat html_dir "p") "test-pkg" in
162 Unix.mkdir pkg_dir 0o755;
163
164 (* Create existing final directory *)
165 let final_dir = Filename.concat pkg_dir "1.0.0" in
166 Unix.mkdir final_dir 0o755;
167 write_file (Filename.concat final_dir "index.html") "original content";
168
169 (* Create staging directory (simulating failed build) *)
170 let staging_dir = Filename.concat pkg_dir "1.0.0.new" in
171 Unix.mkdir staging_dir 0o755;
172 write_file (Filename.concat staging_dir "index.html") "incomplete content";
173
174 Day10_lib.Atomic_swap.rollback ~html_dir ~pkg:"test-pkg" ~version:"1.0.0" ~blessed:true ~universe:"";
175
176 (* Verify staging was removed but original preserved *)
177 assert (not (file_exists staging_dir));
178 assert (file_exists final_dir);
179 assert (read_file (Filename.concat final_dir "index.html") = "original content");
180
181 teardown ();
182 Printf.printf "PASS: test_rollback_preserves_existing\n%!"
183
184(** Test: commit returns false when staging doesn't exist *)
185let test_commit_no_staging () =
186 let html_dir = setup () in
187 let pkg_dir = Filename.concat (Filename.concat html_dir "p") "test-pkg" in
188 Unix.mkdir pkg_dir 0o755;
189
190 let result = Day10_lib.Atomic_swap.commit ~html_dir ~pkg:"test-pkg" ~version:"1.0.0" ~blessed:true ~universe:"" in
191 assert (not result);
192
193 teardown ();
194 Printf.printf "PASS: test_commit_no_staging\n%!"
195
196let () =
197 Printf.printf "Running atomic swap tests...\n%!";
198 test_cleanup_stale_new ();
199 test_cleanup_stale_old ();
200 test_cleanup_preserves_normal ();
201 test_swap_paths_blessed ();
202 test_swap_paths_universe ();
203 test_commit_success ();
204 test_commit_replaces_existing ();
205 test_rollback_preserves_existing ();
206 test_commit_no_staging ();
207 Printf.printf "\nAll atomic swap tests passed!\n%!"