• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

google / alioth / 20530427066

26 Dec 2025 09:30PM UTC coverage: 26.438% (+0.2%) from 26.249%
20530427066

push

github

Lencerf
test(cli): add tests for argument parsing

Signed-off-by: Changyuan Lyu <changyuanl@google.com>

425 of 1146 branches covered (37.09%)

Branch coverage included in aggregate %.

0 of 1 new or added line in 1 file covered. (0.0%)

85 existing lines in 4 files now uncovered.

3583 of 14014 relevant lines covered (25.57%)

23.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.0
/alioth-cli/src/boot.rs
1
// Copyright 2025 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     https://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
use std::collections::HashMap;
16
use std::ffi::CString;
17
use std::mem;
18
use std::path::{Path, PathBuf};
19

20
use alioth::board::{BoardConfig, CpuConfig};
21
#[cfg(target_arch = "x86_64")]
22
use alioth::device::fw_cfg::FwCfgItemParam;
23
use alioth::errors::{DebugTrace, trace_error};
24
#[cfg(target_os = "macos")]
25
use alioth::hv::Hvf;
26
#[cfg(target_os = "linux")]
27
use alioth::hv::Kvm;
28
use alioth::hv::{Coco, HvConfig};
29
use alioth::loader::{Executable, Payload};
30
use alioth::mem::{MemBackend, MemConfig};
31
#[cfg(target_os = "linux")]
32
use alioth::vfio::{CdevParam, ContainerParam, GroupParam, IoasParam};
33
use alioth::virtio::dev::balloon::BalloonParam;
34
use alioth::virtio::dev::blk::BlkFileParam;
35
use alioth::virtio::dev::entropy::EntropyParam;
36
use alioth::virtio::worker::WorkerApi;
37
use alioth::vm::Machine;
38
use alioth::vm::config::{BlkParam, Config, FsParam, NetParam, VsockParam};
39
use clap::Args;
40
use serde_aco::help_text;
41
use snafu::{ResultExt, Snafu};
42

43
use crate::objects::{DOC_OBJECTS, parse_objects};
44

45
#[trace_error]
UNCOV
46
#[derive(Snafu, DebugTrace)]
×
47
#[snafu(module, context(suffix(false)))]
48
pub enum Error {
49
    #[snafu(display("Failed to parse {arg}"))]
50
    ParseArg {
51
        arg: String,
52
        error: serde_aco::Error,
53
    },
54
    #[snafu(display("Failed to parse objects"), context(false))]
55
    ParseObjects { source: crate::objects::Error },
56
    #[cfg(target_os = "linux")]
57
    #[snafu(display("Failed to access system hypervisor"))]
58
    Hypervisor { source: alioth::hv::Error },
59
    #[snafu(display("Failed to create a VM"))]
60
    CreateVm { source: alioth::vm::Error },
61
    #[snafu(display("Failed to boot a VM"))]
62
    BootVm { source: alioth::vm::Error },
63
    #[snafu(display("VM did not shutdown peacefully"))]
64
    WaitVm { source: alioth::vm::Error },
65
}
66

67
#[derive(Args, Debug, Clone, Default)]
68
#[command(arg_required_else_help = true, alias("run"))]
69
pub struct BootArgs {
70
    #[arg(long, help(
71
        help_text::<HvConfig>("Specify the Hypervisor to run on.")
72
    ), value_name = "HV")]
73
    hypervisor: Option<String>,
74

75
    /// Path to a Linux kernel image.
76
    #[arg(short, long, value_name = "PATH")]
77
    kernel: Option<Box<Path>>,
78

79
    /// Path to an ELF kernel with PVH note.
80
    #[cfg(target_arch = "x86_64")]
81
    #[arg(long, value_name = "PATH")]
82
    pvh: Option<Box<Path>>,
83

84
    /// Path to a firmware image.
85
    #[arg(long, short, value_name = "PATH")]
86
    firmware: Option<Box<Path>>,
87

88
    /// Command line to pass to the kernel, e.g. `console=ttyS0`.
89
    #[arg(short, long, alias = "cmd-line", value_name = "ARGS")]
90
    cmdline: Option<CString>,
91

92
    /// Path to an initramfs image.
93
    #[arg(short, long, value_name = "PATH")]
94
    initramfs: Option<Box<Path>>,
95

96
    /// DEPRECATED: Use --cpu instead.
97
    #[arg(long, default_value_t = 1)]
98
    num_cpu: u16,
99

100
    #[arg(short('p'), long, help(
101
        help_text::<CpuConfig>("Configure the VCPUs of the guest.")
102
    ))]
103
    cpu: Option<Box<str>>,
104

105
    /// DEPRECATED: Use --memory instead.
106
    #[arg(long, default_value = "1G")]
107
    mem_size: String,
108

109
    #[arg(short, long, help(
110
        help_text::<MemConfig>("Specify the memory of the guest.")
111
    ))]
112
    memory: Option<String>,
113

114
    /// Add a pvpanic device.
115
    #[arg(long)]
116
    pvpanic: bool,
117

118
    #[cfg(target_arch = "x86_64")]
119
    #[arg(long, help(
120
        help_text::<FwCfgItemParam>("Add an extra item to the fw_cfg device.")
121
    ), value_name = "ITEM")]
122
    fw_cfg: Vec<String>,
123

124
    /// Add a VirtIO entropy device.
125
    #[arg(long)]
126
    entropy: bool,
127

128
    #[arg(long, help(
129
        help_text::<NetParam>("Add a VirtIO net device.")
130
    ))]
131
    net: Vec<String>,
132

133
    #[arg(long, help(
134
        help_text::<BlkParam>("Add a VirtIO block device.")
135
    ))]
136
    blk: Vec<String>,
137

138
    #[arg(long, help(
139
        help_text::<Coco>("Enable confidential compute supported by host platform.")
140
    ))]
141
    coco: Option<String>,
142

143
    #[arg(long, help(
144
        help_text::<FsParam>("Add a VirtIO filesystem device.")
145
    ))]
146
    fs: Vec<String>,
147

148
    #[arg(long, help(
149
        help_text::<VsockParam>("Add a VirtIO vsock device.")
150
    ))]
151
    vsock: Option<String>,
152

153
    #[cfg(target_os = "linux")]
154
    #[arg(long, help(help_text::<CdevParam>(
155
        "Assign a host PCI device to the guest using IOMMUFD API."
156
    ) ))]
157
    vfio_cdev: Vec<String>,
158

159
    #[cfg(target_os = "linux")]
160
    #[arg(long, help(help_text::<IoasParam>("Create a new IO address space.")))]
161
    vfio_ioas: Vec<String>,
162

163
    #[cfg(target_os = "linux")]
164
    #[arg(long, help(help_text::<GroupParam>(
165
        "Assign a host PCI device to the guest using legacy VFIO API."
166
    )))]
167
    vfio_group: Vec<String>,
168

169
    #[cfg(target_os = "linux")]
170
    #[arg(long, help(help_text::<ContainerParam>("Add a new VFIO container.")))]
171
    vfio_container: Vec<String>,
172

173
    #[arg(long)]
174
    #[arg(long, help(help_text::<BalloonParam>("Add a VirtIO balloon device.")))]
175
    balloon: Option<String>,
176

177
    #[arg(short, long("object"), help = DOC_OBJECTS, value_name = "OBJECT")]
178
    objects: Vec<String>,
179
}
180

181
fn parse_net_arg(arg: &str, objects: &HashMap<&str, &str>) -> serde_aco::Result<NetParam> {
8✔
182
    #[cfg(target_os = "linux")]
183
    if let Ok(param) = serde_aco::from_args(arg, objects) {
6✔
184
        Ok(param)
2✔
185
    } else {
186
        let param = serde_aco::from_args(arg, objects)?;
4✔
187
        Ok(NetParam::Tap(param))
4✔
188
    }
189

190
    #[cfg(target_os = "macos")]
191
    serde_aco::from_args(arg, objects)
2✔
192
}
8✔
193

194
fn parse_blk_arg(arg: &str, objects: &HashMap<&str, &str>) -> BlkParam {
17✔
195
    if let Ok(param) = serde_aco::from_args(arg, objects) {
17✔
196
        param
11✔
197
    } else if let Ok(param) = serde_aco::from_args(arg, objects) {
6✔
198
        BlkParam::File(param)
3✔
199
    } else {
200
        eprintln!("Please update the cmd line to --blk file,path={arg}");
3✔
201
        BlkParam::File(BlkFileParam {
3✔
202
            path: PathBuf::from(arg).into(),
3✔
203
            readonly: false,
3✔
204
            api: WorkerApi::Mio,
3✔
205
        })
3✔
206
    }
207
}
17✔
208

209
fn parse_mem_arg(
8✔
210
    arg: Option<String>,
8✔
211
    mem_size: String,
8✔
212
    objects: &HashMap<&str, &str>,
8✔
213
) -> Result<MemConfig, Error> {
8✔
214
    let config = if let Some(arg) = arg {
8✔
215
        serde_aco::from_args(&arg, objects).context(error::ParseArg { arg })?
5✔
216
    } else {
217
        #[cfg(target_os = "linux")]
218
        eprintln!("Please update the cmd line to --memory size={mem_size},backend=memfd");
2✔
219
        MemConfig {
220
            size: serde_aco::from_args(&mem_size, objects)
3✔
221
                .context(error::ParseArg { arg: mem_size })?,
3✔
222
            #[cfg(target_os = "linux")]
223
            backend: MemBackend::Memfd,
2✔
224
            #[cfg(not(target_os = "linux"))]
225
            backend: MemBackend::Anonymous,
1✔
226
            ..Default::default()
3✔
227
        }
228
    };
229
    Ok(config)
8✔
230
}
8✔
231

232
fn parse_cpu_arg(
9✔
233
    arg: Option<Box<str>>,
9✔
234
    num_cpu: u16,
9✔
235
    objects: &HashMap<&str, &str>,
9✔
236
) -> Result<CpuConfig, Error> {
9✔
237
    let config = if let Some(arg) = arg {
9✔
238
        serde_aco::from_args(&arg, objects).context(error::ParseArg { arg })?
6✔
239
    } else {
240
        eprintln!("Please update the cmd line to --cpu count={num_cpu}");
3✔
241
        CpuConfig {
3✔
242
            count: num_cpu,
3✔
243
            ..Default::default()
3✔
244
        }
3✔
245
    };
246
    Ok(config)
9✔
247
}
9✔
248

249
fn parse_payload_arg(args: &mut BootArgs) -> Payload {
7✔
250
    let mut payload = Payload {
7✔
251
        firmware: args.firmware.take(),
7✔
252
        initramfs: args.initramfs.take(),
7✔
253
        cmdline: args.cmdline.take(),
7✔
254
        ..Default::default()
7✔
255
    };
7✔
256
    payload.executable = args.kernel.take().map(Executable::Linux);
7✔
257
    #[cfg(target_arch = "x86_64")]
258
    if payload.executable.is_none() {
3✔
259
        payload.executable = args.pvh.take().map(Executable::Pvh);
1✔
260
    }
2✔
261
    payload
7✔
262
}
7✔
263

264
fn parse_args(mut args: BootArgs, objects: HashMap<&str, &str>) -> Result<Config, Error> {
3✔
265
    let payload = parse_payload_arg(&mut args);
3✔
266

267
    let mut board_config = BoardConfig::default();
3✔
268
    if let Some(arg) = args.coco {
3!
UNCOV
269
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
×
UNCOV
270
        board_config.coco = Some(param);
×
271
    };
3✔
272
    board_config.mem = parse_mem_arg(args.memory, args.mem_size, &objects)?;
3✔
273
    board_config.cpu = parse_cpu_arg(args.cpu, args.num_cpu, &objects)?;
3✔
274

275
    let mut config = Config {
3✔
276
        board: board_config,
3✔
277
        pvpanic: args.pvpanic,
3✔
278
        payload,
3✔
279
        ..Default::default()
3✔
280
    };
3✔
281

282
    #[cfg(target_arch = "x86_64")]
283
    for arg in args.fw_cfg {
2✔
284
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
2✔
285
        config.fw_cfg.push(param);
2✔
286
    }
287

288
    if args.entropy {
3!
289
        config.entropy = Some(EntropyParam::default());
3✔
290
    }
3✔
291

292
    for arg in args.net {
5✔
293
        let param = parse_net_arg(&arg, &objects).context(error::ParseArg { arg })?;
5✔
294
        config.net.push(param);
5✔
295
    }
296

297
    for arg in args.blk {
6✔
298
        let param = parse_blk_arg(&arg, &objects);
6✔
299
        config.blk.push(param);
6✔
300
    }
6✔
301

302
    for arg in args.fs {
5✔
303
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
5✔
304
        config.fs.push(param);
5✔
305
    }
306

307
    if let Some(arg) = args.vsock {
3!
308
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
3✔
309
        config.vsock = Some(param);
3✔
310
    }
×
311

312
    if let Some(arg) = args.balloon {
3!
313
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
3✔
314
        config.balloon = Some(param);
3✔
315
    }
×
316

317
    #[cfg(target_os = "linux")]
318
    for arg in args.vfio_ioas {
2✔
319
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
2✔
320
        config.vfio_ioas.push(param);
2✔
321
    }
322
    #[cfg(target_os = "linux")]
323
    for arg in args.vfio_cdev {
2✔
324
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
2✔
325
        config.vfio_cdev.push(param);
2✔
326
    }
327
    #[cfg(target_os = "linux")]
328
    for arg in args.vfio_container {
2✔
329
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
2✔
330
        config.vfio_container.push(param);
2✔
331
    }
332
    #[cfg(target_os = "linux")]
333
    for arg in args.vfio_group {
2✔
334
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
2✔
335
        config.vfio_group.push(param);
2✔
336
    }
337

338
    Ok(config)
3✔
339
}
3✔
340

341
pub fn boot(mut args: BootArgs) -> Result<(), Error> {
×
342
    let object_args = mem::take(&mut args.objects);
×
343
    let objects = parse_objects(&object_args)?;
×
344

UNCOV
345
    let hv_config = if let Some(arg) = args.hypervisor.take() {
×
346
        serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?
×
347
    } else {
348
        HvConfig::default()
×
349
    };
UNCOV
350
    let hypervisor = match hv_config {
×
351
        #[cfg(target_os = "linux")]
352
        HvConfig::Kvm(kvm_config) => Kvm::new(kvm_config).context(error::Hypervisor)?,
×
353
        #[cfg(target_os = "macos")]
354
        HvConfig::Hvf => Hvf {},
×
355
    };
356

UNCOV
357
    let config = parse_args(args, objects)?;
×
358

UNCOV
359
    let vm = Machine::new(hypervisor, config).context(error::CreateVm)?;
×
360

UNCOV
361
    vm.boot().context(error::BootVm)?;
×
362
    vm.wait().context(error::WaitVm)?;
×
UNCOV
363
    Ok(())
×
364
}
×
365

366
#[cfg(test)]
367
#[path = "boot_test.rs"]
368
mod tests;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc