xref: /aosp_15_r20/external/flashrom/util/flashrom_tester/src/tester.rs (revision 0d6140be3aa665ecc836e8907834fcd3e3b018fc)
1 //
2 // Copyright 2019, Google Inc.
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 //    * Redistributions of source code must retain the above copyright
10 // notice, this list of conditions and the following disclaimer.
11 //    * Redistributions in binary form must reproduce the above
12 // copyright notice, this list of conditions and the following disclaimer
13 // in the documentation and/or other materials provided with the
14 // distribution.
15 //    * Neither the name of Google Inc. nor the names of its
16 // contributors may be used to endorse or promote products derived from
17 // this software without specific prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 //
31 // Alternatively, this software may be distributed under the terms of the
32 // GNU General Public License ("GPL") version 2 as published by the Free
33 // Software Foundation.
34 //
35 
36 use super::rand_util;
37 use super::types;
38 use super::utils::{self, LayoutSizes};
39 use flashrom::FlashromError;
40 use flashrom::{FlashChip, Flashrom};
41 use libflashrom::FlashromFlags;
42 use serde_json::json;
43 use std::fs::File;
44 use std::io::Write;
45 use std::path::Path;
46 use std::path::PathBuf;
47 use std::sync::atomic::{AtomicBool, Ordering};
48 
49 // type-signature comes from the return type of lib.rs workers.
50 type TestError = Box<dyn std::error::Error>;
51 pub type TestResult = Result<(), TestError>;
52 
53 pub struct TestEnv<'a> {
54     chip_type: FlashChip,
55     /// Flashrom instantiation information.
56     ///
57     /// Where possible, prefer to use methods on the TestEnv rather than delegating
58     /// to the raw flashrom functions.
59     pub cmd: &'a dyn Flashrom,
60     layout: LayoutSizes,
61 
62     pub wp: WriteProtectState<'a>,
63     /// The path to a file containing the flash contents at test start.
64     original_flash_contents: PathBuf,
65     /// The path to a file containing flash-sized random data
66     random_data: PathBuf,
67     /// The path to a file containing layout data.
68     pub layout_file: PathBuf,
69 }
70 
71 impl<'a> TestEnv<'a> {
create( chip_type: FlashChip, cmd: &'a dyn Flashrom, print_layout: bool, ) -> Result<Self, FlashromError>72     pub fn create(
73         chip_type: FlashChip,
74         cmd: &'a dyn Flashrom,
75         print_layout: bool,
76     ) -> Result<Self, FlashromError> {
77         let rom_sz = cmd.get_size()?;
78         let out = TestEnv {
79             chip_type,
80             cmd,
81             layout: utils::get_layout_sizes(rom_sz)?,
82             wp: WriteProtectState::from_hardware(cmd, chip_type)?,
83             original_flash_contents: "/tmp/flashrom_tester_golden.bin".into(),
84             random_data: "/tmp/random_content.bin".into(),
85             layout_file: create_layout_file(rom_sz, Path::new("/tmp/"), print_layout),
86         };
87         let flags = FlashromFlags::default();
88         info!("Set flags: {}", flags);
89         out.cmd.set_flags(&flags);
90 
91         info!("Stashing golden image for verification/recovery on completion");
92         out.cmd.read_into_file(&out.original_flash_contents)?;
93         out.cmd.verify_from_file(&out.original_flash_contents)?;
94 
95         info!("Generating random flash-sized data");
96         rand_util::gen_rand_testdata(&out.random_data, rom_sz as usize)
97             .map_err(|io_err| format!("I/O error writing random data file: {:#}", io_err))?;
98 
99         Ok(out)
100     }
101 
run_test<T: TestCase>(&mut self, test: T) -> TestResult102     pub fn run_test<T: TestCase>(&mut self, test: T) -> TestResult {
103         let name = test.get_name();
104         info!("Beginning test: {}", name);
105         let out = test.run(self);
106         info!("Completed test: {}; result {:?}", name, out);
107         out
108     }
109 
chip_type(&self) -> FlashChip110     pub fn chip_type(&self) -> FlashChip {
111         // This field is not public because it should be immutable to tests,
112         // so this getter enforces that it is copied.
113         self.chip_type
114     }
115 
116     /// Return the path to a file that contains random data and is the same size
117     /// as the flash chip.
random_data_file(&self) -> &Path118     pub fn random_data_file(&self) -> &Path {
119         &self.random_data
120     }
121 
layout(&self) -> &LayoutSizes122     pub fn layout(&self) -> &LayoutSizes {
123         &self.layout
124     }
125 
126     /// Return true if the current Flash contents are the same as the golden image
127     /// that was present at the start of testing.
is_golden(&self) -> bool128     pub fn is_golden(&self) -> bool {
129         self.cmd
130             .verify_from_file(&self.original_flash_contents)
131             .is_ok()
132     }
133 
134     /// Do whatever is necessary to make the current Flash contents the same as they
135     /// were at the start of testing.
ensure_golden(&mut self) -> Result<(), FlashromError>136     pub fn ensure_golden(&mut self) -> Result<(), FlashromError> {
137         self.wp.set_hw(false)?.set_sw(false)?;
138         self.cmd.write_from_file(&self.original_flash_contents)?;
139         Ok(())
140     }
141 
142     /// Attempt to erase the flash.
erase(&self) -> Result<(), FlashromError>143     pub fn erase(&self) -> Result<(), FlashromError> {
144         self.cmd.erase()?;
145         Ok(())
146     }
147 
148     /// Verify that the current Flash contents are the same as the file at the given
149     /// path.
150     ///
151     /// Returns Err if they are not the same.
verify(&self, contents_path: &Path) -> Result<(), FlashromError>152     pub fn verify(&self, contents_path: &Path) -> Result<(), FlashromError> {
153         self.cmd.verify_from_file(contents_path)?;
154         Ok(())
155     }
156 }
157 
158 impl<'a> Drop for TestEnv<'a> {
drop(&mut self)159     fn drop(&mut self) {
160         info!("Verifying flash remains unmodified");
161         if !self.is_golden() {
162             warn!("ROM seems to be in a different state at finish; restoring original");
163             if let Err(e) = self.ensure_golden() {
164                 error!("Failed to write back golden image: {:?}", e);
165             }
166         }
167     }
168 }
169 
170 struct WriteProtect {
171     hw: bool,
172     sw: bool,
173 }
174 
175 /// RAII handle for setting write protect in either hardware or software.
176 ///
177 /// Given an instance, the state of either write protect can be modified by calling
178 /// `set`. When it goes out of scope, the write protects will be returned
179 /// to the state they had then it was created.
180 pub struct WriteProtectState<'a> {
181     current: WriteProtect,
182     initial: WriteProtect,
183     cmd: &'a dyn Flashrom,
184     fc: FlashChip,
185 }
186 
187 impl<'a> WriteProtectState<'a> {
188     /// Initialize a state from the current state of the hardware.
from_hardware(cmd: &'a dyn Flashrom, fc: FlashChip) -> Result<Self, FlashromError>189     pub fn from_hardware(cmd: &'a dyn Flashrom, fc: FlashChip) -> Result<Self, FlashromError> {
190         let hw = Self::get_hw(cmd)?;
191         let sw = Self::get_sw(cmd)?;
192         info!("Initial write protect state: HW={} SW={}", hw, sw);
193 
194         Ok(WriteProtectState {
195             current: WriteProtect { hw, sw },
196             initial: WriteProtect { hw, sw },
197             cmd,
198             fc,
199         })
200     }
201 
202     /// Get the actual hardware write protect state.
get_hw(cmd: &dyn Flashrom) -> Result<bool, String>203     fn get_hw(cmd: &dyn Flashrom) -> Result<bool, String> {
204         if cmd.can_control_hw_wp() {
205             super::utils::get_hardware_wp()
206         } else {
207             Ok(false)
208         }
209     }
210 
211     /// Get the actual software write protect state.
get_sw(cmd: &dyn Flashrom) -> Result<bool, FlashromError>212     fn get_sw(cmd: &dyn Flashrom) -> Result<bool, FlashromError> {
213         let b = cmd.wp_status(true)?;
214         Ok(b)
215     }
216 
217     /// Return true if the current programmer supports setting the hardware
218     /// write protect.
219     ///
220     /// If false, calls to set_hw() will do nothing.
can_control_hw_wp(&self) -> bool221     pub fn can_control_hw_wp(&self) -> bool {
222         self.cmd.can_control_hw_wp()
223     }
224 
225     /// Set the software write protect and check that the state is as expected.
set_sw(&mut self, enable: bool) -> Result<&mut Self, String>226     pub fn set_sw(&mut self, enable: bool) -> Result<&mut Self, String> {
227         info!("set_sw request={}, current={}", enable, self.current.sw);
228         if self.current.sw != enable {
229             self.cmd
230                 .wp_toggle(/* en= */ enable)
231                 .map_err(|e| e.to_string())?;
232         }
233         if Self::get_sw(self.cmd).map_err(|e| e.to_string())? != enable {
234             Err(format!(
235                 "Software write protect did not change state to {} when requested",
236                 enable
237             ))
238         } else {
239             self.current.sw = enable;
240             Ok(self)
241         }
242     }
243 
244     // Set software write protect with a custom range
set_range(&mut self, range: (i64, i64), enable: bool) -> Result<&mut Self, String>245     pub fn set_range(&mut self, range: (i64, i64), enable: bool) -> Result<&mut Self, String> {
246         info!("set_range request={}, current={}", enable, self.current.sw);
247         self.cmd
248             .wp_range(range, enable)
249             .map_err(|e| e.to_string())?;
250         let actual_state = Self::get_sw(self.cmd).map_err(|e| e.to_string())?;
251         if actual_state != enable {
252             Err(format!(
253                 "set_range request={}, real={}",
254                 enable, actual_state
255             ))
256         } else {
257             self.current.sw = enable;
258             Ok(self)
259         }
260     }
261 
262     /// Set the hardware write protect if supported and check that the state is as expected.
set_hw(&mut self, enable: bool) -> Result<&mut Self, String>263     pub fn set_hw(&mut self, enable: bool) -> Result<&mut Self, String> {
264         info!("set_hw request={}, current={}", enable, self.current.hw);
265         if self.can_control_hw_wp() {
266             if self.current.hw != enable {
267                 super::utils::toggle_hw_wp(/* dis= */ !enable)?;
268             }
269             // toggle_hw_wp does check this, but we might not have called toggle_hw_wp so check again.
270             if Self::get_hw(self.cmd)? != enable {
271                 return Err(format!(
272                     "Hardware write protect did not change state to {} when requested",
273                     enable
274                 ));
275             }
276         } else {
277             info!(
278                 "Ignoring attempt to set hardware WP with {:?} programmer",
279                 self.fc
280             );
281         }
282         self.current.hw = enable;
283         Ok(self)
284     }
285 
286     /// Reset the hardware to what it was when this state was created, reporting errors.
287     ///
288     /// This behaves exactly like allowing a state to go out of scope, but it can return
289     /// errors from that process rather than panicking.
close(mut self) -> Result<(), String>290     pub fn close(mut self) -> Result<(), String> {
291         let out = self.drop_internal();
292         // We just ran drop, don't do it again
293         std::mem::forget(self);
294         out
295     }
296 
297     /// Sets both write protects to the state they had when this state was created.
drop_internal(&mut self) -> Result<(), String>298     fn drop_internal(&mut self) -> Result<(), String> {
299         // Toggle both protects back to their initial states.
300         // Software first because we can't change it once hardware is enabled.
301         if self.set_sw(self.initial.sw).is_err() {
302             self.set_hw(false)?;
303             self.set_sw(self.initial.sw)?;
304         }
305         self.set_hw(self.initial.hw)?;
306 
307         Ok(())
308     }
309 }
310 
311 impl<'a> Drop for WriteProtectState<'a> {
drop(&mut self)312     fn drop(&mut self) {
313         self.drop_internal()
314             .expect("Error while dropping WriteProtectState")
315     }
316 }
317 
318 pub trait TestCase {
get_name(&self) -> &str319     fn get_name(&self) -> &str;
expected_result(&self) -> TestConclusion320     fn expected_result(&self) -> TestConclusion;
run(&self, env: &mut TestEnv) -> TestResult321     fn run(&self, env: &mut TestEnv) -> TestResult;
322 }
323 
324 impl<S: AsRef<str>, F: Fn(&mut TestEnv) -> TestResult> TestCase for (S, F) {
get_name(&self) -> &str325     fn get_name(&self) -> &str {
326         self.0.as_ref()
327     }
328 
expected_result(&self) -> TestConclusion329     fn expected_result(&self) -> TestConclusion {
330         TestConclusion::Pass
331     }
332 
run(&self, env: &mut TestEnv) -> TestResult333     fn run(&self, env: &mut TestEnv) -> TestResult {
334         (self.1)(env)
335     }
336 }
337 
338 impl<T: TestCase + ?Sized> TestCase for &T {
get_name(&self) -> &str339     fn get_name(&self) -> &str {
340         (*self).get_name()
341     }
342 
expected_result(&self) -> TestConclusion343     fn expected_result(&self) -> TestConclusion {
344         (*self).expected_result()
345     }
346 
run(&self, env: &mut TestEnv) -> TestResult347     fn run(&self, env: &mut TestEnv) -> TestResult {
348         (*self).run(env)
349     }
350 }
351 
352 #[allow(dead_code)]
353 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
354 pub enum TestConclusion {
355     Pass,
356     Fail,
357     UnexpectedPass,
358     UnexpectedFail,
359 }
360 
361 pub struct ReportMetaData {
362     pub chip_name: String,
363     pub os_release: String,
364     pub cros_release: String,
365     pub system_info: String,
366     pub bios_info: String,
367 }
368 
decode_test_result(res: TestResult, con: TestConclusion) -> (TestConclusion, Option<TestError>)369 fn decode_test_result(res: TestResult, con: TestConclusion) -> (TestConclusion, Option<TestError>) {
370     use TestConclusion::*;
371 
372     match (res, con) {
373         (Ok(_), Fail) => (UnexpectedPass, None),
374         (Err(e), Pass) => (UnexpectedFail, Some(e)),
375         _ => (Pass, None),
376     }
377 }
378 
create_layout_file(rom_sz: i64, tmp_dir: &Path, print_layout: bool) -> PathBuf379 fn create_layout_file(rom_sz: i64, tmp_dir: &Path, print_layout: bool) -> PathBuf {
380     info!("Calculate ROM partition sizes & Create the layout file.");
381     let layout_sizes = utils::get_layout_sizes(rom_sz).expect("Could not partition rom");
382 
383     let layout_file = tmp_dir.join("layout.file");
384     let mut f = File::create(&layout_file).expect("Could not create layout file");
385     let mut buf: Vec<u8> = vec![];
386     utils::construct_layout_file(&mut buf, &layout_sizes).expect("Could not construct layout file");
387 
388     f.write_all(&buf).expect("Writing layout file failed");
389     if print_layout {
390         info!(
391             "Dumping layout file as requested:\n{}",
392             String::from_utf8_lossy(&buf)
393         );
394     }
395     layout_file
396 }
397 
run_all_tests<T, TS>( chip: FlashChip, cmd: &dyn Flashrom, ts: TS, terminate_flag: Option<&AtomicBool>, print_layout: bool, ) -> Vec<(String, (TestConclusion, Option<TestError>))> where T: TestCase + Copy, TS: IntoIterator<Item = T>,398 pub fn run_all_tests<T, TS>(
399     chip: FlashChip,
400     cmd: &dyn Flashrom,
401     ts: TS,
402     terminate_flag: Option<&AtomicBool>,
403     print_layout: bool,
404 ) -> Vec<(String, (TestConclusion, Option<TestError>))>
405 where
406     T: TestCase + Copy,
407     TS: IntoIterator<Item = T>,
408 {
409     let mut env =
410         TestEnv::create(chip, cmd, print_layout).expect("Failed to set up test environment");
411 
412     let mut results = Vec::new();
413     for t in ts {
414         if terminate_flag
415             .map(|b| b.load(Ordering::Acquire))
416             .unwrap_or(false)
417         {
418             break;
419         }
420 
421         let result = decode_test_result(env.run_test(t), t.expected_result());
422         results.push((t.get_name().into(), result));
423     }
424     results
425 }
426 
427 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
428 pub enum OutputFormat {
429     Pretty,
430     Json,
431 }
432 
433 impl std::str::FromStr for OutputFormat {
434     type Err = ();
435 
from_str(s: &str) -> Result<Self, Self::Err>436     fn from_str(s: &str) -> Result<Self, Self::Err> {
437         use OutputFormat::*;
438 
439         if s.eq_ignore_ascii_case("pretty") {
440             Ok(Pretty)
441         } else if s.eq_ignore_ascii_case("json") {
442             Ok(Json)
443         } else {
444             Err(())
445         }
446     }
447 }
448 
collate_all_test_runs( truns: &[(String, (TestConclusion, Option<TestError>))], meta_data: ReportMetaData, format: OutputFormat, )449 pub fn collate_all_test_runs(
450     truns: &[(String, (TestConclusion, Option<TestError>))],
451     meta_data: ReportMetaData,
452     format: OutputFormat,
453 ) {
454     match format {
455         OutputFormat::Pretty => {
456             let color = if atty::is(atty::Stream::Stdout) {
457                 types::COLOR
458             } else {
459                 types::NOCOLOR
460             };
461             println!();
462             println!("  =============================");
463             println!("  =====  AVL qual RESULTS  ====");
464             println!("  =============================");
465             println!();
466             println!("  %---------------------------%");
467             println!("   os release: {}", meta_data.os_release);
468             println!("   cros release: {}", meta_data.cros_release);
469             println!("   chip name: {}", meta_data.chip_name);
470             println!("   system info: \n{}", meta_data.system_info);
471             println!("   bios info: \n{}", meta_data.bios_info);
472             println!("  %---------------------------%");
473             println!();
474 
475             for trun in truns.iter() {
476                 let (name, (result, error)) = trun;
477                 if *result != TestConclusion::Pass {
478                     println!(
479                         " {} {}",
480                         style!(format!(" <+> {} test:", name), color.bold, color),
481                         style_dbg!(result, color.red, color)
482                     );
483                     match error {
484                         None => {}
485                         Some(e) => info!(" - {} failure details:\n{}", name, e.to_string()),
486                     };
487                 } else {
488                     println!(
489                         " {} {}",
490                         style!(format!(" <+> {} test:", name), color.bold, color),
491                         style_dbg!(result, color.green, color)
492                     );
493                 }
494             }
495             println!();
496         }
497         OutputFormat::Json => {
498             use serde_json::{Map, Value};
499 
500             let mut all_pass = true;
501             let mut tests = Map::<String, Value>::new();
502             for (name, (result, error)) in truns {
503                 let passed = *result == TestConclusion::Pass;
504                 all_pass &= passed;
505 
506                 let error = match error {
507                     Some(e) => Value::String(format!("{:#?}", e)),
508                     None => Value::Null,
509                 };
510 
511                 assert!(
512                     !tests.contains_key(name),
513                     "Found multiple tests named {:?}",
514                     name
515                 );
516                 tests.insert(
517                     name.into(),
518                     json!({
519                         "pass": passed,
520                         "error": error,
521                     }),
522                 );
523             }
524 
525             let json = json!({
526                 "pass": all_pass,
527                 "metadata": {
528                     "os_release": meta_data.os_release,
529                     "chip_name": meta_data.chip_name,
530                     "system_info": meta_data.system_info,
531                     "bios_info": meta_data.bios_info,
532                 },
533                 "tests": tests,
534             });
535             println!("{:#}", json);
536         }
537     }
538 }
539 
540 #[cfg(test)]
541 mod tests {
542     #[test]
decode_test_result()543     fn decode_test_result() {
544         use super::decode_test_result;
545         use super::TestConclusion::*;
546 
547         let (result, err) = decode_test_result(Ok(()), Pass);
548         assert_eq!(result, Pass);
549         assert!(err.is_none());
550 
551         let (result, err) = decode_test_result(Ok(()), Fail);
552         assert_eq!(result, UnexpectedPass);
553         assert!(err.is_none());
554 
555         let (result, err) = decode_test_result(Err("broken".into()), Pass);
556         assert_eq!(result, UnexpectedFail);
557         assert!(err.is_some());
558 
559         let (result, err) = decode_test_result(Err("broken".into()), Fail);
560         assert_eq!(result, Pass);
561         assert!(err.is_none());
562     }
563 
564     #[test]
output_format_round_trip()565     fn output_format_round_trip() {
566         use super::OutputFormat::{self, *};
567 
568         assert_eq!(format!("{:?}", Pretty).parse::<OutputFormat>(), Ok(Pretty));
569         assert_eq!(format!("{:?}", Json).parse::<OutputFormat>(), Ok(Json));
570     }
571 }
572