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