opentitanlib/test_utils/
init.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use anyhow::Result;
6use clap::Args;
7use directories::ProjectDirs;
8use log::LevelFilter;
9use std::env::ArgsOs;
10use std::ffi::OsString;
11use std::io::ErrorKind;
12use std::iter::Iterator;
13use std::path::PathBuf;
14use std::str::FromStr;
15
16use super::bootstrap::Bootstrap;
17use super::load_bitstream::LoadBitstream;
18use crate::app::TransportWrapper;
19use crate::backend;
20
21#[derive(Debug, Args)]
22pub struct InitializeTest {
23    /// Filename of a default flagsfile.  Relative to $XDG_CONFIG_HOME/opentitantool.
24    #[arg(long, value_parser = PathBuf::from_str, default_value = "config")]
25    pub rcfile: PathBuf,
26
27    #[arg(long, default_value = "off")]
28    pub logging: LevelFilter,
29
30    #[command(flatten)]
31    pub backend_opts: backend::BackendOpts,
32
33    #[command(flatten)]
34    pub load_bitstream: LoadBitstream,
35
36    #[command(flatten)]
37    pub bootstrap: Bootstrap,
38}
39
40impl InitializeTest {
41    pub fn init_logging(&self) {
42        let level = self.logging;
43        // The tests might use OpenOCD which uses util::printer so we will get
44        // more useful logging if we log the target instead of the module path
45        if level != LevelFilter::Off {
46            env_logger::Builder::from_default_env()
47                .format_target(true)
48                .format_module_path(false)
49                .format_timestamp_millis()
50                .filter(None, level)
51                .init();
52        }
53    }
54
55    // Given some existing option configuration, maybe re-evaluate command
56    // line options by reading an `rcfile`.
57    pub fn parse_command_line(&self, mut args: ArgsOs) -> Result<Vec<OsString>> {
58        // Initialize the logger if the user requested the non-defualt option.
59        self.init_logging();
60        if self.rcfile.as_os_str().is_empty() {
61            // No rcfile to parse.
62            return Ok(Vec::new());
63        }
64
65        // Construct the rcfile path based on the user's config directory
66        // (ie: $HOME/.config/opentitantool/<filename>).
67        let rcfile = if let Some(base) = ProjectDirs::from("org", "opentitan", "opentitantool") {
68            base.config_dir().join(&self.rcfile)
69        } else {
70            self.rcfile.clone()
71        };
72
73        // argument[0] is the executable name.
74        let mut arguments = vec![args.next().unwrap()];
75
76        // Read in the rcfile and extend the argument list.
77        match std::fs::read_to_string(&rcfile) {
78            Ok(content) => {
79                for line in content.split('\n') {
80                    // Strip basic comments as shellwords won't handle comments.
81                    let (line, _) = line.split_once('#').unwrap_or((line, ""));
82                    arguments.extend(shellwords::split(line)?.iter().map(OsString::from));
83                }
84                Ok(())
85            }
86            Err(e) if e.kind() == ErrorKind::NotFound => {
87                log::warn!("Could not read {:?}. Ignoring.", rcfile);
88                Ok(())
89            }
90            Err(e) => Err(anyhow::Error::new(e).context(format!("Reading file {:?}", rcfile))),
91        }?;
92
93        // Extend the argument list with all remaining command line arguments.
94        arguments.extend(args);
95        Ok(arguments)
96    }
97
98    // Print the result of a command.
99    // If there is an error and `RUST_BACKTRACE=1`, print a backtrace.
100    pub fn print_result(
101        operation: &str,
102        result: Result<Option<Box<dyn erased_serde::Serialize>>>,
103    ) -> Result<()> {
104        match result {
105            Ok(Some(value)) => {
106                log::info!("{}: success.", operation);
107                println!(
108                    "\"{}\": {}",
109                    operation,
110                    serde_json::to_string_pretty(&value)?
111                );
112                Ok(())
113            }
114            Ok(None) => {
115                log::info!("{}: success.", operation);
116                println!("\"{}\": true", operation);
117                Ok(())
118            }
119            Err(e) => {
120                log::info!("{}: {:?}.", operation, e);
121                println!("\"{}\": false", operation);
122                Err(e)
123            }
124        }
125    }
126
127    pub fn init_target(&self) -> Result<TransportWrapper> {
128        // Create the transport interface.
129        let transport = backend::create(&self.backend_opts)?;
130
131        // Set up the default pin configurations as specified in the transport's config file.
132        transport.apply_default_configuration(None)?;
133
134        // Create the UART first to initialize the desired parameters.
135        let _uart = self.bootstrap.options.uart_params.create(&transport)?;
136
137        // Load a bitstream.
138        Self::print_result(
139            "load_bitstream",
140            self.load_bitstream.init(&transport).map(|_| None),
141        )?;
142
143        // Bootstrap an rv32 test program.
144        Self::print_result("bootstrap", self.bootstrap.init(&transport))?;
145        Ok(transport)
146    }
147}