QEMU/KVM virtual machine management via QMP
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

test(qemu): add test_qmp_protocol (78 tests) and test_vm (47 tests)

QMP protocol tests use QEMU QMP spec JSON vectors for version,
greeting, message parsing, command serialization, run_state, errors,
timestamps, and event data.

VM tests cover config builder pattern and to_args argument generation
for network modes, display types, serial variants, disk formats,
chardev, channels, and combined configurations.

+1095 -11
+12 -11
test/test_qemu.ml
··· 608 608 609 609 let () = 610 610 Alcotest.run "qemu" 611 - [ 612 - ("greeting", greeting_tests); 613 - ("success", success_tests); 614 - ("error", error_tests); 615 - ("event", event_tests); 616 - ("command", command_tests); 617 - ("status", status_tests); 618 - ("version", version_tests); 619 - ("session", session_tests); 620 - ("config", config_tests); 621 - ] 611 + ([ 612 + ("greeting", greeting_tests); 613 + ("success", success_tests); 614 + ("error", error_tests); 615 + ("event", event_tests); 616 + ("command", command_tests); 617 + ("status", status_tests); 618 + ("version", version_tests); 619 + ("session", session_tests); 620 + ("config", config_tests); 621 + ] 622 + @ Test_qmp_protocol.suite @ Test_vm.suite)
+409
test/test_qmp_protocol.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Thomas Gazagnaire. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** QMP protocol tests covering aspects not in test_qemu.ml: 7 + - Version codec roundtrips 8 + - Greeting from QEMU 9.0 specification 9 + - Command.make with explicit JSON arguments 10 + - Message edge cases (malformed JSON, missing fields, extra fields) 11 + - Timestamp arithmetic 12 + - Error/Status pretty-printing 13 + - DeviceNotActive error class 14 + 15 + Test vectors are from the upstream QEMU QMP specification 16 + (docs/interop/qmp-spec.rst) and QAPI schema files (qapi/*.json). *) 17 + 18 + module Qmp = Qemu.Qmp 19 + 20 + let parse_json s = 21 + match Jsont_bytesrw.decode_string Jsont.json s with 22 + | Ok json -> json 23 + | Error e -> failwith e 24 + 25 + let encode_json json = 26 + match Jsont_bytesrw.encode_string Jsont.json json with 27 + | Ok s -> s 28 + | Error e -> failwith e 29 + 30 + (* {1 Version codec tests} *) 31 + 32 + let test_version_make_roundtrip () = 33 + (* make -> jsont encode -> jsont decode -> check fields *) 34 + let v = Qmp.Version.make 9 0 0 "v9.0.0" in 35 + let json = Jsont.Json.encode Qmp.Version.jsont v in 36 + match json with 37 + | Ok json -> ( 38 + match Jsont.Json.decode Qmp.Version.jsont json with 39 + | Ok v' -> 40 + Alcotest.(check int) "major" 9 v'.qemu.major; 41 + Alcotest.(check int) "minor" 0 v'.qemu.minor; 42 + Alcotest.(check int) "micro" 0 v'.qemu.micro; 43 + Alcotest.(check string) "package" "v9.0.0" v'.package 44 + | Error e -> Alcotest.failf "decode: %s" e) 45 + | Error e -> Alcotest.failf "encode: %s" e 46 + 47 + let test_version_high_numbers () = 48 + let v = Qmp.Version.make 99 12 3 "custom-build-2025" in 49 + let s = Fmt.str "%a" Qmp.Version.pp v in 50 + Alcotest.(check string) "version" "99.12.3 (custom-build-2025)" s 51 + 52 + let test_version_jsont_from_json () = 53 + (* Parse version JSON directly via the jsont codec *) 54 + let json = 55 + parse_json 56 + {|{"qemu": {"major": 7, "minor": 2, "micro": 5}, "package": "Debian 1:7.2+dfsg-7"}|} 57 + in 58 + match Jsont.Json.decode Qmp.Version.jsont json with 59 + | Ok v -> 60 + Alcotest.(check int) "major" 7 v.qemu.major; 61 + Alcotest.(check int) "minor" 2 v.qemu.minor; 62 + Alcotest.(check int) "micro" 5 v.qemu.micro; 63 + Alcotest.(check string) "package" "Debian 1:7.2+dfsg-7" v.package 64 + | Error e -> Alcotest.failf "decode: %s" e 65 + 66 + let test_version_extra_fields_ignored () = 67 + (* Jsont skip_unknown should tolerate extra fields *) 68 + let json = 69 + parse_json 70 + {|{"qemu": {"major": 8, "minor": 0, "micro": 0, "extra": true}, "package": "", "build-date": "2023-04"}|} 71 + in 72 + match Jsont.Json.decode Qmp.Version.jsont json with 73 + | Ok v -> 74 + Alcotest.(check int) "major" 8 v.qemu.major; 75 + Alcotest.(check string) "package" "" v.package 76 + | Error e -> Alcotest.failf "decode: %s" e 77 + 78 + (* {1 Greeting tests -- QEMU 9.0 spec vector} *) 79 + 80 + let test_greeting_qemu9 () = 81 + (* From QEMU 9.0 docs/interop/qmp-spec.rst *) 82 + let json = 83 + {|{"QMP": {"version": {"qemu": {"major": 9, "minor": 0, "micro": 0}, "package": "v9.0.0"}, "capabilities": ["oob"]}}|} 84 + in 85 + match Qmp.Message.of_string json with 86 + | Ok (Qmp.Message.Greeting g) -> 87 + let v = Qmp.Greeting.version g in 88 + Alcotest.(check int) "major" 9 v.qemu.major; 89 + Alcotest.(check int) "minor" 0 v.qemu.minor; 90 + Alcotest.(check int) "micro" 0 v.qemu.micro; 91 + Alcotest.(check string) "package" "v9.0.0" v.package; 92 + Alcotest.(check (list string)) 93 + "capabilities" [ "oob" ] 94 + (Qmp.Greeting.capabilities g) 95 + | Ok _ -> Alcotest.fail "Expected Greeting" 96 + | Error e -> Alcotest.fail e 97 + 98 + let test_greeting_multiple_caps () = 99 + (* Hypothetical future QEMU with multiple capabilities *) 100 + let json = 101 + {|{"QMP": {"version": {"qemu": {"major": 10, "minor": 1, "micro": 0}, "package": ""}, "capabilities": ["oob", "multi-process"]}}|} 102 + in 103 + match Qmp.Message.of_string json with 104 + | Ok (Qmp.Message.Greeting g) -> 105 + Alcotest.(check (list string)) 106 + "capabilities" [ "oob"; "multi-process" ] 107 + (Qmp.Greeting.capabilities g) 108 + | Ok _ -> Alcotest.fail "Expected Greeting" 109 + | Error e -> Alcotest.fail e 110 + 111 + (* {1 Message edge cases} *) 112 + 113 + let test_message_malformed_json () = 114 + match Qmp.Message.of_string "{not valid json" with 115 + | Error _ -> () 116 + | Ok _ -> Alcotest.fail "Should reject malformed JSON" 117 + 118 + let test_message_empty_object () = 119 + match Qmp.Message.of_string "{}" with 120 + | Error _ -> () 121 + | Ok _ -> Alcotest.fail "Should reject empty object" 122 + 123 + let test_message_unknown_toplevel () = 124 + (* A valid JSON object but not a recognized QMP message type *) 125 + match Qmp.Message.of_string {|{"foo": "bar"}|} with 126 + | Error _ -> () 127 + | Ok _ -> Alcotest.fail "Should reject unknown message type" 128 + 129 + let test_message_null () = 130 + match Qmp.Message.of_string "null" with 131 + | Error _ -> () 132 + | Ok _ -> Alcotest.fail "Should reject null" 133 + 134 + let test_message_array () = 135 + match Qmp.Message.of_string "[1,2,3]" with 136 + | Error _ -> () 137 + | Ok _ -> Alcotest.fail "Should reject array" 138 + 139 + let test_message_empty_string () = 140 + match Qmp.Message.of_string "" with 141 + | Error _ -> () 142 + | Ok _ -> Alcotest.fail "Should reject empty string" 143 + 144 + let test_message_success_extra_fields () = 145 + (* Real QEMU may send extra fields we don't know about; skip_unknown handles *) 146 + let json = 147 + {|{"return": {"status": "running"}, "id": "1", "extra-debug": true}|} 148 + in 149 + match Qmp.Message.of_string json with 150 + | Ok (Qmp.Message.Success s) -> 151 + Alcotest.(check (option string)) "id" (Some "1") s.success_id 152 + | Ok _ -> Alcotest.fail "Expected Success" 153 + | Error e -> Alcotest.fail e 154 + 155 + (* {1 Command with arguments} *) 156 + 157 + let test_command_with_arguments () = 158 + (* device_del command with device argument *) 159 + let args = parse_json {|{"device": "virtio-net-pci-0"}|} in 160 + let cmd = Qmp.Command.make ~arguments:args "device_del" in 161 + let json = Qmp.Command.to_json cmd in 162 + let s = encode_json json in 163 + (* The serialized form should contain "device_del" and the argument *) 164 + Alcotest.(check bool) "has execute" true (String.length s > 0); 165 + (* Roundtrip through codec *) 166 + match Jsont_bytesrw.decode_string Qmp.Command.jsont s with 167 + | Ok _ -> () 168 + | Error e -> Alcotest.failf "roundtrip: %s" e 169 + 170 + let test_command_with_id_and_arguments () = 171 + let args = parse_json {|{"protocol": "tcp", "hostname": "localhost"}|} in 172 + let cmd = Qmp.Command.make ~arguments:args ~id:"migrate-1" "migrate" in 173 + let json = Qmp.Command.to_json cmd in 174 + let s = encode_json json in 175 + match Jsont_bytesrw.decode_string Qmp.Command.jsont s with 176 + | Ok _ -> () 177 + | Error e -> Alcotest.failf "roundtrip: %s" e 178 + 179 + let test_command_blockdev_add () = 180 + (* More complex arguments -- blockdev-add with nested config *) 181 + let args = 182 + parse_json 183 + {|{"driver": "qcow2", "node-name": "disk0", "file": {"driver": "file", "filename": "/tmp/test.qcow2"}}|} 184 + in 185 + let cmd = Qmp.Command.make ~arguments:args "blockdev-add" in 186 + let json = Qmp.Command.to_json cmd in 187 + let s = encode_json json in 188 + match Jsont_bytesrw.decode_string Qmp.Command.jsont s with 189 + | Ok _ -> () 190 + | Error e -> Alcotest.failf "roundtrip: %s" e 191 + 192 + (* {1 Status run_state string roundtrip} *) 193 + 194 + let test_run_state_unknown_string () = 195 + (* Unknown state string should return None *) 196 + Alcotest.(check bool) 197 + "unknown" true 198 + (Option.is_none (Qmp.Status.run_state_of_string "bogus")); 199 + Alcotest.(check bool) 200 + "empty" true 201 + (Option.is_none (Qmp.Status.run_state_of_string "")); 202 + Alcotest.(check bool) 203 + "case sensitive" true 204 + (Option.is_none (Qmp.Status.run_state_of_string "Running")) 205 + 206 + let test_status_pp_singlestep () = 207 + (* Status.pp with singlestep=true *) 208 + let json = 209 + parse_json {|{"running": true, "singlestep": true, "status": "running"}|} 210 + in 211 + match Qmp.Status.of_json json with 212 + | Ok s -> 213 + let str = Fmt.str "%a" Qmp.Status.pp s in 214 + Alcotest.(check string) "pp" "running (singlestep)" str 215 + | Error e -> Alcotest.fail e 216 + 217 + let test_status_pp_no_singlestep () = 218 + let json = 219 + parse_json 220 + {|{"running": false, "singlestep": false, "status": "inmigrate"}|} 221 + in 222 + match Qmp.Status.of_json json with 223 + | Ok s -> 224 + let str = Fmt.str "%a" Qmp.Status.pp s in 225 + Alcotest.(check string) "pp" "inmigrate" str 226 + | Error e -> Alcotest.fail e 227 + 228 + let test_status_io_error () = 229 + let json = 230 + parse_json {|{"running": false, "singlestep": false, "status": "io-error"}|} 231 + in 232 + match Qmp.Status.of_json json with 233 + | Ok s -> 234 + Alcotest.(check bool) "running" false s.running; 235 + Alcotest.(check string) 236 + "status" "io-error" 237 + (Qmp.Status.string_of_run_state s.status) 238 + | Error e -> Alcotest.fail e 239 + 240 + let test_status_guest_panicked () = 241 + let json = 242 + parse_json 243 + {|{"running": false, "singlestep": false, "status": "guest-panicked"}|} 244 + in 245 + match Qmp.Status.of_json json with 246 + | Ok s -> 247 + Alcotest.(check string) 248 + "status" "guest-panicked" 249 + (Qmp.Status.string_of_run_state s.status) 250 + | Error e -> Alcotest.fail e 251 + 252 + (* {1 Error parsing -- additional error classes} *) 253 + 254 + let test_error_device_not_active () = 255 + let json = 256 + {|{"error": {"class": "DeviceNotActive", "desc": "No block device 'ide0-1-0'"}}|} 257 + in 258 + match Qmp.Message.of_string json with 259 + | Ok (Qmp.Message.Error e) -> 260 + Alcotest.(check string) "class" "DeviceNotActive" e.qmp_error.class_; 261 + Alcotest.(check string) 262 + "desc" "No block device 'ide0-1-0'" e.qmp_error.desc 263 + | Ok _ -> Alcotest.fail "Expected Error" 264 + | Error e -> Alcotest.fail e 265 + 266 + let test_error_device_not_found () = 267 + let json = 268 + {|{"error": {"class": "DeviceNotFound", "desc": "Device 'net0' not found"}}|} 269 + in 270 + match Qmp.Message.of_string json with 271 + | Ok (Qmp.Message.Error e) -> 272 + Alcotest.(check string) "class" "DeviceNotFound" e.qmp_error.class_ 273 + | Ok _ -> Alcotest.fail "Expected Error" 274 + | Error e -> Alcotest.fail e 275 + 276 + let test_error_pp () = 277 + let err = { Qmp.Error.class_ = "GenericError"; desc = "something broke" } in 278 + let s = Fmt.str "%a" Qmp.Error.pp err in 279 + Alcotest.(check string) "error pp" "GenericError: something broke" s 280 + 281 + (* {1 Timestamp tests} *) 282 + 283 + let test_timestamp_to_float_zero () = 284 + let ts = { Qmp.Timestamp.seconds = 0; microseconds = 0 } in 285 + Alcotest.(check (float 0.0)) "zero" 0.0 (Qmp.Timestamp.to_float ts) 286 + 287 + let test_timestamp_to_float_only_seconds () = 288 + let ts = { Qmp.Timestamp.seconds = 1000; microseconds = 0 } in 289 + Alcotest.(check (float 0.0)) "seconds only" 1000.0 (Qmp.Timestamp.to_float ts) 290 + 291 + let test_timestamp_to_float_only_microseconds () = 292 + let ts = { Qmp.Timestamp.seconds = 0; microseconds = 500000 } in 293 + Alcotest.(check (float 0.001)) 294 + "microseconds only" 0.5 295 + (Qmp.Timestamp.to_float ts) 296 + 297 + let test_timestamp_to_float_combined () = 298 + (* From the QMP spec POWERDOWN example *) 299 + let ts = { Qmp.Timestamp.seconds = 1401385907; microseconds = 422329 } in 300 + Alcotest.(check (float 0.001)) 301 + "combined" 1401385907.422329 302 + (Qmp.Timestamp.to_float ts) 303 + 304 + let test_timestamp_jsont_roundtrip () = 305 + let json = parse_json {|{"seconds": 1267040730, "microseconds": 682951}|} in 306 + match Jsont.Json.decode Qmp.Timestamp.jsont json with 307 + | Ok ts -> 308 + Alcotest.(check int) "seconds" 1267040730 ts.seconds; 309 + Alcotest.(check int) "microseconds" 682951 ts.microseconds 310 + | Error e -> Alcotest.failf "decode: %s" e 311 + 312 + (* {1 Event data extraction} *) 313 + 314 + let test_event_with_complex_data () = 315 + (* BLOCK_JOB_COMPLETED event from qapi/block-core.json *) 316 + let json = 317 + {|{"event": "BLOCK_JOB_COMPLETED", "data": {"type": "stream", "device": "virtio0", "len": 10737418240, "offset": 10737418240, "speed": 0}, "timestamp": {"seconds": 1267061043, "microseconds": 959568}}|} 318 + in 319 + match Qmp.Message.of_string json with 320 + | Ok (Qmp.Message.Event ev) -> 321 + Alcotest.(check string) "event" "BLOCK_JOB_COMPLETED" (Qmp.Event.name ev); 322 + Alcotest.(check bool) "has data" true (Option.is_some (Qmp.Event.data ev)) 323 + | Ok _ -> Alcotest.fail "Expected Event" 324 + | Error e -> Alcotest.fail e 325 + 326 + let test_event_guest_panicked () = 327 + (* GUEST_PANICKED event from qapi/run-state.json *) 328 + let json = 329 + {|{"event": "GUEST_PANICKED", "data": {"action": "pause"}, "timestamp": {"seconds": 1648811810, "microseconds": 123456}}|} 330 + in 331 + match Qmp.Message.of_string json with 332 + | Ok (Qmp.Message.Event ev) -> 333 + Alcotest.(check string) "event" "GUEST_PANICKED" (Qmp.Event.name ev); 334 + Alcotest.(check (float 0.001)) 335 + "timestamp" 1648811810.123456 (Qmp.Event.timestamp ev) 336 + | Ok _ -> Alcotest.fail "Expected Event" 337 + | Error e -> Alcotest.fail e 338 + 339 + (* {1 Test suites} *) 340 + 341 + let suite = 342 + [ 343 + ( "version_codec", 344 + [ 345 + Alcotest.test_case "make roundtrip" `Quick test_version_make_roundtrip; 346 + Alcotest.test_case "high numbers" `Quick test_version_high_numbers; 347 + Alcotest.test_case "jsont from JSON" `Quick test_version_jsont_from_json; 348 + Alcotest.test_case "extra fields ignored" `Quick 349 + test_version_extra_fields_ignored; 350 + ] ); 351 + ( "greeting_extended", 352 + [ 353 + Alcotest.test_case "QEMU 9.0 (qmp-spec.rst)" `Quick test_greeting_qemu9; 354 + Alcotest.test_case "multiple capabilities" `Quick 355 + test_greeting_multiple_caps; 356 + ] ); 357 + ( "message", 358 + [ 359 + Alcotest.test_case "malformed JSON" `Quick test_message_malformed_json; 360 + Alcotest.test_case "empty object" `Quick test_message_empty_object; 361 + Alcotest.test_case "unknown toplevel key" `Quick 362 + test_message_unknown_toplevel; 363 + Alcotest.test_case "null" `Quick test_message_null; 364 + Alcotest.test_case "array" `Quick test_message_array; 365 + Alcotest.test_case "empty string" `Quick test_message_empty_string; 366 + Alcotest.test_case "success with extra fields" `Quick 367 + test_message_success_extra_fields; 368 + ] ); 369 + ( "command_args", 370 + [ 371 + Alcotest.test_case "with arguments" `Quick test_command_with_arguments; 372 + Alcotest.test_case "with id and arguments" `Quick 373 + test_command_with_id_and_arguments; 374 + Alcotest.test_case "blockdev-add nested args" `Quick 375 + test_command_blockdev_add; 376 + ] ); 377 + ( "run_state", 378 + [ 379 + Alcotest.test_case "unknown string" `Quick test_run_state_unknown_string; 380 + Alcotest.test_case "pp singlestep" `Quick test_status_pp_singlestep; 381 + Alcotest.test_case "pp no singlestep" `Quick 382 + test_status_pp_no_singlestep; 383 + Alcotest.test_case "io-error" `Quick test_status_io_error; 384 + Alcotest.test_case "guest-panicked" `Quick test_status_guest_panicked; 385 + ] ); 386 + ( "error_classes", 387 + [ 388 + Alcotest.test_case "DeviceNotActive" `Quick test_error_device_not_active; 389 + Alcotest.test_case "DeviceNotFound" `Quick test_error_device_not_found; 390 + Alcotest.test_case "Error.pp" `Quick test_error_pp; 391 + ] ); 392 + ( "timestamp", 393 + [ 394 + Alcotest.test_case "zero" `Quick test_timestamp_to_float_zero; 395 + Alcotest.test_case "seconds only" `Quick 396 + test_timestamp_to_float_only_seconds; 397 + Alcotest.test_case "microseconds only" `Quick 398 + test_timestamp_to_float_only_microseconds; 399 + Alcotest.test_case "combined" `Quick test_timestamp_to_float_combined; 400 + Alcotest.test_case "jsont roundtrip" `Quick 401 + test_timestamp_jsont_roundtrip; 402 + ] ); 403 + ( "event_data", 404 + [ 405 + Alcotest.test_case "BLOCK_JOB_COMPLETED" `Quick 406 + test_event_with_complex_data; 407 + Alcotest.test_case "GUEST_PANICKED" `Quick test_event_guest_panicked; 408 + ] ); 409 + ]
+674
test/test_vm.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Thomas Gazagnaire. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** VM configuration and argument generation tests. 7 + 8 + Tests Config builder pattern and to_args output for various configurations. 9 + Covers aspects NOT in test_qemu.ml: network modes (tap/bridge), display 10 + types (vnc/gtk/sdl/spice), chardevs, channels, serial variants, QMP socket 11 + path, architecture-specific args, deprecated with_kvm, extra args, disk 12 + format/readonly details. *) 13 + 14 + module Config = Qemu.Vm.Config 15 + 16 + let find_arg_value key args = 17 + let rec loop = function 18 + | [] -> None 19 + | k :: v :: _ when k = key -> Some v 20 + | _ :: rest -> loop rest 21 + in 22 + loop args 23 + 24 + let has_arg key args = List.mem key args 25 + 26 + let has_arg_value key value args = 27 + match find_arg_value key args with Some v -> v = value | None -> false 28 + 29 + let count_arg key args = List.length (List.filter (fun s -> s = key) args) 30 + 31 + let string_contains ~sub s = 32 + let len_sub = String.length sub in 33 + let len_s = String.length s in 34 + if len_sub > len_s then false 35 + else 36 + let rec check i = 37 + if i > len_s - len_sub then false 38 + else if String.sub s i len_sub = sub then true 39 + else check (i + 1) 40 + in 41 + check 0 42 + 43 + let collect_device_args args = 44 + let rec loop = function 45 + | [] -> [] 46 + | "-device" :: v :: rest -> v :: loop rest 47 + | _ :: rest -> loop rest 48 + in 49 + loop args 50 + 51 + let collect_drive_values args = 52 + let rec loop = function 53 + | [] -> [] 54 + | "-drive" :: v :: rest -> v :: loop rest 55 + | _ :: rest -> loop rest 56 + in 57 + loop args 58 + 59 + (* {1 Config.default tests} *) 60 + 61 + let test_default_name () = 62 + let config = Config.default ~name:"myvm" in 63 + let args = Config.to_args config in 64 + Alcotest.(check (option string)) 65 + "name" (Some "myvm") 66 + (find_arg_value "-name" args) 67 + 68 + let test_default_cpus () = 69 + let config = Config.default ~name:"test" in 70 + let args = Config.to_args config in 71 + Alcotest.(check (option string)) 72 + "cpus" (Some "1") 73 + (find_arg_value "-smp" args) 74 + 75 + let test_default_memory () = 76 + let config = Config.default ~name:"test" in 77 + let args = Config.to_args config in 78 + Alcotest.(check (option string)) 79 + "memory" (Some "512") (find_arg_value "-m" args) 80 + 81 + let test_default_has_qmp () = 82 + let config = Config.default ~name:"test" in 83 + let args = Config.to_args config in 84 + Alcotest.(check bool) "has -qmp" true (has_arg "-qmp" args); 85 + match find_arg_value "-qmp" args with 86 + | Some v -> 87 + Alcotest.(check bool) "qmp contains path" true (String.length v > 0) 88 + | None -> Alcotest.fail "missing -qmp" 89 + 90 + let test_default_serial_stdio () = 91 + let config = Config.default ~name:"test" in 92 + let args = Config.to_args config in 93 + Alcotest.(check bool) 94 + "serial stdio" true 95 + (has_arg_value "-serial" "stdio" args) 96 + 97 + let test_default_display_none () = 98 + (* Default display is None -> -nographic *) 99 + let config = Config.default ~name:"test" in 100 + let args = Config.to_args config in 101 + Alcotest.(check bool) "nographic" true (has_arg "-nographic" args) 102 + 103 + (* {1 Builder pattern tests} *) 104 + 105 + let test_with_cpus () = 106 + let config = Config.default ~name:"test" |> Config.with_cpus 8 in 107 + let args = Config.to_args config in 108 + Alcotest.(check (option string)) 109 + "cpus" (Some "8") 110 + (find_arg_value "-smp" args) 111 + 112 + let test_with_memory_mb () = 113 + let config = Config.default ~name:"test" |> Config.with_memory_mb 4096 in 114 + let args = Config.to_args config in 115 + Alcotest.(check (option string)) 116 + "memory" (Some "4096") (find_arg_value "-m" args) 117 + 118 + let test_with_machine () = 119 + let config = Config.default ~name:"test" |> Config.with_machine "microvm" in 120 + let args = Config.to_args config in 121 + Alcotest.(check (option string)) 122 + "machine" (Some "microvm") 123 + (find_arg_value "-machine" args) 124 + 125 + let test_with_cpu () = 126 + let config = Config.default ~name:"test" |> Config.with_cpu "max" in 127 + let args = Config.to_args config in 128 + Alcotest.(check (option string)) 129 + "cpu" (Some "max") 130 + (find_arg_value "-cpu" args) 131 + 132 + let test_with_arch_x86_64 () = 133 + let config = Config.default ~name:"test" |> Config.with_arch Config.X86_64 in 134 + let args = Config.to_args config in 135 + (* x86_64 defaults to q35 machine and host cpu *) 136 + Alcotest.(check (option string)) 137 + "machine" (Some "q35") 138 + (find_arg_value "-machine" args); 139 + Alcotest.(check (option string)) 140 + "cpu" (Some "host") 141 + (find_arg_value "-cpu" args) 142 + 143 + let test_with_arch_aarch64 () = 144 + let config = Config.default ~name:"test" |> Config.with_arch Config.Aarch64 in 145 + let args = Config.to_args config in 146 + (* aarch64 defaults to virt machine and host cpu *) 147 + Alcotest.(check (option string)) 148 + "machine" (Some "virt") 149 + (find_arg_value "-machine" args); 150 + Alcotest.(check (option string)) 151 + "cpu" (Some "host") 152 + (find_arg_value "-cpu" args) 153 + 154 + let test_with_accel_kvm () = 155 + let config = Config.default ~name:"test" |> Config.with_accel Config.KVM in 156 + let args = Config.to_args config in 157 + Alcotest.(check (option string)) 158 + "accel" (Some "kvm") 159 + (find_arg_value "-accel" args) 160 + 161 + let test_with_accel_hvf () = 162 + let config = Config.default ~name:"test" |> Config.with_accel Config.HVF in 163 + let args = Config.to_args config in 164 + Alcotest.(check (option string)) 165 + "accel" (Some "hvf") 166 + (find_arg_value "-accel" args) 167 + 168 + let test_with_accel_tcg () = 169 + let config = Config.default ~name:"test" |> Config.with_accel Config.TCG in 170 + let args = Config.to_args config in 171 + Alcotest.(check (option string)) 172 + "accel" (Some "tcg") 173 + (find_arg_value "-accel" args) 174 + 175 + let test_with_kvm_deprecated () = 176 + (* with_kvm true sets KVM, false sets TCG *) 177 + let kvm = Config.default ~name:"test" |> Config.with_kvm true in 178 + let tcg = Config.default ~name:"test" |> Config.with_kvm false in 179 + let kvm_args = Config.to_args kvm in 180 + let tcg_args = Config.to_args tcg in 181 + Alcotest.(check (option string)) 182 + "kvm" (Some "kvm") 183 + (find_arg_value "-accel" kvm_args); 184 + Alcotest.(check (option string)) 185 + "tcg" (Some "tcg") 186 + (find_arg_value "-accel" tcg_args) 187 + 188 + (* {1 to_args: network tests} *) 189 + 190 + let test_network_user () = 191 + let config = Config.default ~name:"test" |> Config.with_network Config.User in 192 + let args = Config.to_args config in 193 + Alcotest.(check bool) "has -netdev" true (has_arg "-netdev" args); 194 + match find_arg_value "-netdev" args with 195 + | Some v -> 196 + Alcotest.(check bool) 197 + "user netdev" true 198 + (String.length v >= 4 && String.sub v 0 4 = "user") 199 + | None -> Alcotest.fail "missing -netdev" 200 + 201 + let test_network_tap () = 202 + let config = 203 + Config.default ~name:"test" 204 + |> Config.with_network (Config.Tap { name = "tap0"; script = None }) 205 + in 206 + let args = Config.to_args config in 207 + match find_arg_value "-netdev" args with 208 + | Some v -> 209 + Alcotest.(check bool) 210 + "tap netdev" true 211 + (String.length v >= 3 && String.sub v 0 3 = "tap"); 212 + (* ifname=tap0 should be present *) 213 + Alcotest.(check bool) 214 + "has ifname" true 215 + (string_contains ~sub:"ifname=tap0" v); 216 + (* script=no should be present when script is None *) 217 + Alcotest.(check bool) 218 + "has script=no" true 219 + (string_contains ~sub:"script=no" v) 220 + | None -> Alcotest.fail "missing -netdev" 221 + 222 + let test_network_tap_with_script () = 223 + let config = 224 + Config.default ~name:"test" 225 + |> Config.with_network 226 + (Config.Tap { name = "tap1"; script = Some "/etc/qemu-ifup" }) 227 + in 228 + let args = Config.to_args config in 229 + match find_arg_value "-netdev" args with 230 + | Some v -> 231 + Alcotest.(check bool) 232 + "has script path" true 233 + (string_contains ~sub:"script=/etc/qemu-ifup" v) 234 + | None -> Alcotest.fail "missing -netdev" 235 + 236 + let test_network_bridge () = 237 + let config = 238 + Config.default ~name:"test" 239 + |> Config.with_network (Config.Bridge { name = "br0" }) 240 + in 241 + let args = Config.to_args config in 242 + match find_arg_value "-netdev" args with 243 + | Some v -> 244 + Alcotest.(check bool) 245 + "bridge netdev" true 246 + (String.length v >= 6 && String.sub v 0 6 = "bridge"); 247 + Alcotest.(check bool) "has br=br0" true (string_contains ~sub:"br=br0" v) 248 + | None -> Alcotest.fail "missing -netdev" 249 + 250 + (* {1 to_args: display tests} *) 251 + 252 + let test_display_none () = 253 + let config = Config.default ~name:"test" |> Config.with_display Config.None in 254 + let args = Config.to_args config in 255 + Alcotest.(check bool) "nographic" true (has_arg "-nographic" args); 256 + Alcotest.(check bool) "no -display" false (has_arg "-display" args) 257 + 258 + let test_display_gtk () = 259 + let config = Config.default ~name:"test" |> Config.with_display Config.Gtk in 260 + let args = Config.to_args config in 261 + Alcotest.(check bool) "display gtk" true (has_arg_value "-display" "gtk" args); 262 + Alcotest.(check bool) "no -nographic" false (has_arg "-nographic" args) 263 + 264 + let test_display_sdl () = 265 + let config = Config.default ~name:"test" |> Config.with_display Config.Sdl in 266 + let args = Config.to_args config in 267 + Alcotest.(check bool) "display sdl" true (has_arg_value "-display" "sdl" args) 268 + 269 + let test_display_vnc () = 270 + let config = 271 + Config.default ~name:"test" |> Config.with_display (Config.Vnc { port = 5 }) 272 + in 273 + let args = Config.to_args config in 274 + Alcotest.(check (option string)) 275 + "vnc" (Some ":5") 276 + (find_arg_value "-vnc" args) 277 + 278 + let test_display_spice () = 279 + let config = 280 + Config.default ~name:"test" 281 + |> Config.with_display (Config.Spice { port = 5900 }) 282 + in 283 + let args = Config.to_args config in 284 + match find_arg_value "-spice" args with 285 + | Some v -> 286 + Alcotest.(check bool) "has port" true (string_contains ~sub:"port=5900" v) 287 + | None -> Alcotest.fail "missing -spice" 288 + 289 + (* {1 to_args: serial tests} *) 290 + 291 + let test_serial_stdio () = 292 + let config = Config.default ~name:"test" |> Config.with_serial `Stdio in 293 + let args = Config.to_args config in 294 + Alcotest.(check bool) 295 + "serial stdio" true 296 + (has_arg_value "-serial" "stdio" args) 297 + 298 + let test_serial_pty () = 299 + let config = Config.default ~name:"test" |> Config.with_serial `Pty in 300 + let args = Config.to_args config in 301 + Alcotest.(check bool) "serial pty" true (has_arg_value "-serial" "pty" args) 302 + 303 + let test_serial_none () = 304 + let config = Config.default ~name:"test" |> Config.with_serial `None in 305 + let args = Config.to_args config in 306 + (* `None serial should not add -serial at all *) 307 + Alcotest.(check bool) "no -serial" false (has_arg "-serial" args) 308 + 309 + let test_serial_socket () = 310 + let config = 311 + Config.default ~name:"test" 312 + |> Config.with_serial (`Socket "/tmp/console.sock") 313 + in 314 + let args = Config.to_args config in 315 + (* Should add -chardev for the socket and -serial chardev:ID *) 316 + Alcotest.(check bool) "has -serial" true (has_arg "-serial" args); 317 + Alcotest.(check bool) "has -chardev" true (has_arg "-chardev" args) 318 + 319 + (* {1 to_args: QMP socket tests} *) 320 + 321 + let test_qmp_socket_default () = 322 + let config = Config.default ~name:"myvm" in 323 + let args = Config.to_args config in 324 + match find_arg_value "-qmp" args with 325 + | Some v -> 326 + Alcotest.(check bool) 327 + "unix prefix" true 328 + (String.length v >= 5 && String.sub v 0 5 = "unix:"); 329 + Alcotest.(check bool) 330 + "server,nowait" true 331 + (string_contains ~sub:"server,nowait" v) 332 + | None -> Alcotest.fail "missing -qmp" 333 + 334 + let test_qmp_socket_custom () = 335 + let config = 336 + Config.default ~name:"test" 337 + |> Config.with_qmp_socket "/var/run/qemu/test.sock" 338 + in 339 + let args = Config.to_args config in 340 + match find_arg_value "-qmp" args with 341 + | Some v -> 342 + Alcotest.(check bool) 343 + "has custom path" true 344 + (string_contains ~sub:"/var/run/qemu/test.sock" v) 345 + | None -> Alcotest.fail "missing -qmp" 346 + 347 + (* {1 to_args: disk format and readonly tests} *) 348 + 349 + let test_disk_qcow2 () = 350 + let config = 351 + Config.default ~name:"test" 352 + |> Config.with_disk ~format:`Qcow2 "/tmp/disk.qcow2" 353 + in 354 + let args = Config.to_args config in 355 + match find_arg_value "-drive" args with 356 + | Some v -> 357 + Alcotest.(check bool) 358 + "has format=qcow2" true 359 + (string_contains ~sub:"format=qcow2" v) 360 + | None -> Alcotest.fail "missing -drive" 361 + 362 + let test_disk_raw () = 363 + let config = 364 + Config.default ~name:"test" |> Config.with_disk ~format:`Raw "/tmp/disk.img" 365 + in 366 + let args = Config.to_args config in 367 + match find_arg_value "-drive" args with 368 + | Some v -> 369 + Alcotest.(check bool) 370 + "has format=raw" true 371 + (string_contains ~sub:"format=raw" v) 372 + | None -> Alcotest.fail "missing -drive" 373 + 374 + let test_disk_auto () = 375 + let config = 376 + Config.default ~name:"test" 377 + |> Config.with_disk ~format:`Auto "/tmp/disk.img" 378 + in 379 + let args = Config.to_args config in 380 + match find_arg_value "-drive" args with 381 + | Some v -> 382 + Alcotest.(check bool) 383 + "has format=auto" true 384 + (string_contains ~sub:"format=auto" v) 385 + | None -> Alcotest.fail "missing -drive" 386 + 387 + let test_disk_readonly () = 388 + let config = 389 + Config.default ~name:"test" 390 + |> Config.with_disk ~readonly:true "/tmp/cdrom.iso" 391 + in 392 + let args = Config.to_args config in 393 + match find_arg_value "-drive" args with 394 + | Some v -> 395 + Alcotest.(check bool) 396 + "has readonly=on" true 397 + (string_contains ~sub:"readonly=on" v) 398 + | None -> Alcotest.fail "missing -drive" 399 + 400 + let test_disk_readwrite () = 401 + let config = 402 + Config.default ~name:"test" 403 + |> Config.with_disk ~readonly:false "/tmp/disk.qcow2" 404 + in 405 + let args = Config.to_args config in 406 + match find_arg_value "-drive" args with 407 + | Some v -> 408 + (* readonly=on should NOT be present *) 409 + Alcotest.(check bool) 410 + "no readonly=on" false 411 + (string_contains ~sub:"readonly=on" v) 412 + | None -> Alcotest.fail "missing -drive" 413 + 414 + (* {1 to_args: virtio device suffix tests} *) 415 + 416 + let test_virtio_suffix_virt () = 417 + (* aarch64 with virt machine uses virtio-*-device *) 418 + let config = 419 + Config.default ~name:"test" 420 + |> Config.with_arch Config.Aarch64 421 + |> Config.with_disk "/tmp/disk.qcow2" 422 + in 423 + let args = Config.to_args config in 424 + let device_args = collect_device_args args in 425 + (* Should have virtio-blk-device for disk, virtio-net-device for net *) 426 + Alcotest.(check bool) 427 + "virtio-blk-device" true 428 + (List.exists (string_contains ~sub:"virtio-blk-device") device_args); 429 + Alcotest.(check bool) 430 + "virtio-net-device" true 431 + (List.exists (string_contains ~sub:"virtio-net-device") device_args) 432 + 433 + let test_virtio_suffix_q35 () = 434 + (* x86_64 with q35 machine uses virtio-*-pci *) 435 + let config = 436 + Config.default ~name:"test" 437 + |> Config.with_arch Config.X86_64 438 + |> Config.with_disk "/tmp/disk.qcow2" 439 + in 440 + let args = Config.to_args config in 441 + let device_args = collect_device_args args in 442 + Alcotest.(check bool) 443 + "virtio-blk-pci" true 444 + (List.exists (string_contains ~sub:"virtio-blk-pci") device_args); 445 + Alcotest.(check bool) 446 + "virtio-net-pci" true 447 + (List.exists (string_contains ~sub:"virtio-net-pci") device_args) 448 + 449 + (* {1 to_args: chardev and channel tests} *) 450 + 451 + let test_chardev_socket () = 452 + let chardev : Config.chardev = 453 + { id = "ch0"; backend = Socket { path = "/tmp/ch0.sock"; server = true } } 454 + in 455 + let config = Config.default ~name:"test" |> Config.with_chardev chardev in 456 + let args = Config.to_args config in 457 + match find_arg_value "-chardev" args with 458 + | Some v -> 459 + Alcotest.(check bool) 460 + "socket type" true 461 + (String.length v >= 6 && String.sub v 0 6 = "socket"); 462 + Alcotest.(check bool) "has id=ch0" true (string_contains ~sub:"id=ch0" v); 463 + Alcotest.(check bool) 464 + "server=on" true 465 + (string_contains ~sub:"server=on" v) 466 + | None -> Alcotest.fail "missing -chardev" 467 + 468 + let test_chardev_client () = 469 + let chardev : Config.chardev = 470 + { id = "ch1"; backend = Socket { path = "/tmp/ch1.sock"; server = false } } 471 + in 472 + let config = Config.default ~name:"test" |> Config.with_chardev chardev in 473 + let args = Config.to_args config in 474 + match find_arg_value "-chardev" args with 475 + | Some v -> 476 + (* server=on should NOT be present for client *) 477 + Alcotest.(check bool) 478 + "no server=on" false 479 + (string_contains ~sub:"server=on" v) 480 + | None -> Alcotest.fail "missing -chardev" 481 + 482 + let test_channel () = 483 + let config = 484 + Config.default ~name:"test" 485 + |> Config.with_channel ~chardev_id:"ch0" ~name:"org.qemu.guest_agent.0" 486 + in 487 + let args = Config.to_args config in 488 + (* Should have virtio-serial device and virtserialport *) 489 + let device_args = collect_device_args args in 490 + Alcotest.(check bool) 491 + "virtio-serial" true 492 + (List.exists (string_contains ~sub:"virtio-serial-") device_args); 493 + Alcotest.(check bool) 494 + "virtserialport" true 495 + (List.exists (string_contains ~sub:"virtserialport") device_args) 496 + 497 + (* {1 to_args: extra args tests} *) 498 + 499 + let test_extra_args () = 500 + let config = 501 + Config.default ~name:"test" 502 + |> Config.with_extra_args [ "-daemonize"; "-pidfile"; "/tmp/qemu.pid" ] 503 + in 504 + let args = Config.to_args config in 505 + Alcotest.(check bool) "daemonize" true (has_arg "-daemonize" args); 506 + Alcotest.(check bool) "pidfile" true (has_arg "-pidfile" args); 507 + Alcotest.(check bool) "pidfile value" true (has_arg "/tmp/qemu.pid" args) 508 + 509 + (* {1 to_args: combined configuration} *) 510 + 511 + let test_aarch64_kernel_boot () = 512 + (* Full aarch64 with kernel boot -- verify virt machine and all kernel args *) 513 + let config = 514 + Config.default ~name:"arm-vm" 515 + |> Config.with_arch Config.Aarch64 516 + |> Config.with_accel Config.HVF 517 + |> Config.with_cpus 2 |> Config.with_memory_mb 1024 518 + |> Config.with_kernel "/boot/Image" 519 + |> Config.with_initrd "/boot/initrd.cpio.gz" 520 + |> Config.with_cmdline "console=ttyAMA0 root=/dev/vda rw" 521 + |> Config.with_display Config.None 522 + |> Config.with_serial `Stdio 523 + in 524 + let args = Config.to_args config in 525 + Alcotest.(check (option string)) 526 + "machine" (Some "virt") 527 + (find_arg_value "-machine" args); 528 + Alcotest.(check (option string)) 529 + "accel" (Some "hvf") 530 + (find_arg_value "-accel" args); 531 + Alcotest.(check (option string)) 532 + "cpus" (Some "2") 533 + (find_arg_value "-smp" args); 534 + Alcotest.(check (option string)) 535 + "memory" (Some "1024") (find_arg_value "-m" args); 536 + Alcotest.(check (option string)) 537 + "kernel" (Some "/boot/Image") 538 + (find_arg_value "-kernel" args); 539 + Alcotest.(check (option string)) 540 + "initrd" (Some "/boot/initrd.cpio.gz") 541 + (find_arg_value "-initrd" args); 542 + Alcotest.(check (option string)) 543 + "cmdline" (Some "console=ttyAMA0 root=/dev/vda rw") 544 + (find_arg_value "-append" args); 545 + Alcotest.(check bool) "nographic" true (has_arg "-nographic" args) 546 + 547 + let test_x86_64_kvm_disk () = 548 + (* Full x86_64 with KVM and disk boot *) 549 + let config = 550 + Config.default ~name:"x86-vm" 551 + |> Config.with_arch Config.X86_64 552 + |> Config.with_accel Config.KVM 553 + |> Config.with_cpus 4 |> Config.with_memory_mb 2048 554 + |> Config.with_disk ~format:`Qcow2 "/var/lib/qemu/disk.qcow2" 555 + |> Config.with_display (Config.Vnc { port = 0 }) 556 + |> Config.with_qmp_socket "/var/run/qemu/x86-vm.sock" 557 + in 558 + let args = Config.to_args config in 559 + Alcotest.(check (option string)) 560 + "machine" (Some "q35") 561 + (find_arg_value "-machine" args); 562 + Alcotest.(check (option string)) 563 + "accel" (Some "kvm") 564 + (find_arg_value "-accel" args); 565 + Alcotest.(check (option string)) 566 + "vnc" (Some ":0") 567 + (find_arg_value "-vnc" args); 568 + Alcotest.(check bool) "has -drive" true (has_arg "-drive" args); 569 + (* Verify QMP socket path *) 570 + match find_arg_value "-qmp" args with 571 + | Some v -> 572 + Alcotest.(check bool) 573 + "custom qmp socket" true 574 + (string_contains ~sub:"/var/run/qemu/x86-vm.sock" v) 575 + | None -> Alcotest.fail "missing -qmp" 576 + 577 + let test_no_kernel_no_kernel_args () = 578 + (* Without kernel, -kernel/-initrd/-append should not appear *) 579 + let config = Config.default ~name:"test" in 580 + let args = Config.to_args config in 581 + Alcotest.(check bool) "no -kernel" false (has_arg "-kernel" args); 582 + Alcotest.(check bool) "no -initrd" false (has_arg "-initrd" args); 583 + Alcotest.(check bool) "no -append" false (has_arg "-append" args) 584 + 585 + let test_no_disks_no_drive () = 586 + (* Without disks, -drive should not appear *) 587 + let config = Config.default ~name:"test" in 588 + let args = Config.to_args config in 589 + Alcotest.(check bool) "no -drive" false (has_arg "-drive" args) 590 + 591 + let test_multiple_disks_ordering () = 592 + let config = 593 + Config.default ~name:"test" 594 + |> Config.with_disk ~format:`Qcow2 "/tmp/root.qcow2" 595 + |> Config.with_disk ~format:`Raw "/tmp/data.img" 596 + |> Config.with_disk ~format:`Raw ~readonly:true "/tmp/cdrom.iso" 597 + in 598 + let args = Config.to_args config in 599 + Alcotest.(check int) "3 drives" 3 (count_arg "-drive" args); 600 + (* Verify drive IDs are sequential *) 601 + let drive_values = collect_drive_values args in 602 + Alcotest.(check int) "3 drive values" 3 (List.length drive_values); 603 + Alcotest.(check bool) 604 + "drive0" true 605 + (string_contains ~sub:"id=drive0" (List.nth drive_values 0)); 606 + Alcotest.(check bool) 607 + "drive1" true 608 + (string_contains ~sub:"id=drive1" (List.nth drive_values 1)); 609 + Alcotest.(check bool) 610 + "drive2" true 611 + (string_contains ~sub:"id=drive2" (List.nth drive_values 2)) 612 + 613 + (* {1 Test suites} *) 614 + 615 + let suite = 616 + [ 617 + ( "config_builder", 618 + [ 619 + Alcotest.test_case "default name" `Quick test_default_name; 620 + Alcotest.test_case "default cpus" `Quick test_default_cpus; 621 + Alcotest.test_case "default memory" `Quick test_default_memory; 622 + Alcotest.test_case "default has qmp" `Quick test_default_has_qmp; 623 + Alcotest.test_case "default serial" `Quick test_default_serial_stdio; 624 + Alcotest.test_case "default display none" `Quick 625 + test_default_display_none; 626 + Alcotest.test_case "with_cpus" `Quick test_with_cpus; 627 + Alcotest.test_case "with_memory_mb" `Quick test_with_memory_mb; 628 + Alcotest.test_case "with_machine" `Quick test_with_machine; 629 + Alcotest.test_case "with_cpu" `Quick test_with_cpu; 630 + Alcotest.test_case "with_arch x86_64" `Quick test_with_arch_x86_64; 631 + Alcotest.test_case "with_arch aarch64" `Quick test_with_arch_aarch64; 632 + Alcotest.test_case "with_accel KVM" `Quick test_with_accel_kvm; 633 + Alcotest.test_case "with_accel HVF" `Quick test_with_accel_hvf; 634 + Alcotest.test_case "with_accel TCG" `Quick test_with_accel_tcg; 635 + Alcotest.test_case "with_kvm deprecated" `Quick test_with_kvm_deprecated; 636 + Alcotest.test_case "extra args" `Quick test_extra_args; 637 + ] ); 638 + ( "to_args", 639 + [ 640 + Alcotest.test_case "network user" `Quick test_network_user; 641 + Alcotest.test_case "network tap" `Quick test_network_tap; 642 + Alcotest.test_case "network tap with script" `Quick 643 + test_network_tap_with_script; 644 + Alcotest.test_case "network bridge" `Quick test_network_bridge; 645 + Alcotest.test_case "display none" `Quick test_display_none; 646 + Alcotest.test_case "display gtk" `Quick test_display_gtk; 647 + Alcotest.test_case "display sdl" `Quick test_display_sdl; 648 + Alcotest.test_case "display vnc" `Quick test_display_vnc; 649 + Alcotest.test_case "display spice" `Quick test_display_spice; 650 + Alcotest.test_case "serial stdio" `Quick test_serial_stdio; 651 + Alcotest.test_case "serial pty" `Quick test_serial_pty; 652 + Alcotest.test_case "serial none" `Quick test_serial_none; 653 + Alcotest.test_case "serial socket" `Quick test_serial_socket; 654 + Alcotest.test_case "qmp socket default" `Quick test_qmp_socket_default; 655 + Alcotest.test_case "qmp socket custom" `Quick test_qmp_socket_custom; 656 + Alcotest.test_case "disk qcow2" `Quick test_disk_qcow2; 657 + Alcotest.test_case "disk raw" `Quick test_disk_raw; 658 + Alcotest.test_case "disk auto" `Quick test_disk_auto; 659 + Alcotest.test_case "disk readonly" `Quick test_disk_readonly; 660 + Alcotest.test_case "disk readwrite" `Quick test_disk_readwrite; 661 + Alcotest.test_case "virtio suffix virt" `Quick test_virtio_suffix_virt; 662 + Alcotest.test_case "virtio suffix q35" `Quick test_virtio_suffix_q35; 663 + Alcotest.test_case "chardev socket server" `Quick test_chardev_socket; 664 + Alcotest.test_case "chardev socket client" `Quick test_chardev_client; 665 + Alcotest.test_case "channel" `Quick test_channel; 666 + Alcotest.test_case "no kernel no args" `Quick 667 + test_no_kernel_no_kernel_args; 668 + Alcotest.test_case "no disks no drive" `Quick test_no_disks_no_drive; 669 + Alcotest.test_case "multiple disks ordering" `Quick 670 + test_multiple_disks_ordering; 671 + Alcotest.test_case "aarch64 kernel boot" `Quick test_aarch64_kernel_boot; 672 + Alcotest.test_case "x86_64 kvm disk" `Quick test_x86_64_kvm_disk; 673 + ] ); 674 + ]