diff options
Diffstat (limited to 'src/tests')
-rw-r--r-- | src/tests/extension_var_test.py | 61 | ||||
-rw-r--r-- | src/tests/extensions.rs | 43 | ||||
-rw-r--r-- | src/tests/mod.rs | 10 | ||||
-rw-r--r-- | src/tests/run.rs | 115 |
4 files changed, 229 insertions, 0 deletions
diff --git a/src/tests/extension_var_test.py b/src/tests/extension_var_test.py new file mode 100644 index 0000000..af7ec84 --- /dev/null +++ b/src/tests/extension_var_test.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2020 The Nitrocli Developers +# SPDX-License-Identifier: GPL-3.0-or-later + +from argparse import ( + ArgumentParser, +) +from enum import ( + Enum, +) +from os import ( + environ, +) +from sys import ( + argv, + exit, +) + + +class Action(Enum): + """An action to perform.""" + BINARY = "binary" + MODEL = "model" + NO_CACHE = "no-cache" + SERIAL_NUMBERS = "serial-numbers" + USB_PATH = "usb-path" + VERBOSITY = "verbosity" + + @classmethod + def all(cls): + """Return the list of all the enum members' values.""" + return [x.value for x in cls.__members__.values()] + + +def main(args): + """The extension's main function.""" + parser = ArgumentParser() + parser.add_argument(choices=Action.all(), dest="what") + parser.add_argument("--nitrocli", action="store", default=None) + parser.add_argument("--model", action="store", default=None) + # We deliberately store the argument to this option as a string + # because we can differentiate between None and a valid value, in + # order to verify that it really is supplied. + parser.add_argument("--verbosity", action="store", default=None) + + namespace = parser.parse_args(args[1:]) + # We create a "reverse" mapping from string to variant (e.g., model -> + # MODEL). + options = {v.value: k for k, v in Action.__members__.items()} + try: + var = options[namespace.what] + print(environ[f"NITROCLI_{var}"]) + except KeyError: + return 1 + + return 0 + + +if __name__ == "__main__": + exit(main(argv)) diff --git a/src/tests/extensions.rs b/src/tests/extensions.rs new file mode 100644 index 0000000..a295949 --- /dev/null +++ b/src/tests/extensions.rs @@ -0,0 +1,43 @@ +// extensions.rs + +// Copyright (C) 2020 The Nitrocli Developers +// SPDX-License-Identifier: GPL-3.0-or-later + +use std::env; +use std::fs; + +use super::*; + +#[test] +fn resolve_extensions() -> anyhow::Result<()> { + let dir1 = tempfile::tempdir()?; + let dir2 = tempfile::tempdir()?; + + { + let ext1_path = dir1.path().join("nitrocli-ext1"); + let ext2_path = dir1.path().join("nitrocli-ext2"); + let ext3_path = dir2.path().join("nitrocli-super-1337-extensions111one"); + let _ext1 = fs::File::create(&ext1_path)?; + let _ext2 = fs::File::create(&ext2_path)?; + let _ext3 = fs::File::create(&ext3_path)?; + + let path = env::join_paths(&[dir1.path(), dir2.path()])?; + assert_eq!( + crate::commands::resolve_extension(&path, ffi::OsStr::new("ext1"))?, + ext1_path + ); + assert_eq!( + crate::commands::resolve_extension(&path, ffi::OsStr::new("ext2"))?, + ext2_path + ); + assert_eq!( + crate::commands::resolve_extension(&path, ffi::OsStr::new("super-1337-extensions111one"))?, + ext3_path + ); + + let err = crate::commands::resolve_extension(&ffi::OsStr::new(""), ffi::OsStr::new("ext1")) + .unwrap_err(); + assert_eq!(err.to_string(), "Extension nitrocli-ext1 not found"); + } + Ok(()) +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 65983bb..5e47520 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -9,6 +9,7 @@ use nitrokey_test::test as test_device; mod config; mod encrypted; +mod extensions; mod fill; mod hidden; mod list; @@ -23,6 +24,7 @@ mod unencrypted; struct Nitrocli { model: Option<nitrokey::Model>, + path: Option<ffi::OsString>, admin_pin: Option<ffi::OsString>, user_pin: Option<ffi::OsString>, new_admin_pin: Option<ffi::OsString>, @@ -34,6 +36,7 @@ impl Nitrocli { pub fn new() -> Self { Self { model: None, + path: None, admin_pin: Some(nitrokey::DEFAULT_ADMIN_PIN.into()), user_pin: Some(nitrokey::DEFAULT_USER_PIN.into()), new_admin_pin: None, @@ -54,6 +57,12 @@ impl Nitrocli { self } + /// Set the `PATH` used for looking up extensions. + fn path(mut self, path: impl Into<ffi::OsString>) -> Self { + self.path = Some(path.into()); + self + } + pub fn admin_pin(mut self, pin: impl Into<ffi::OsString>) -> Self { self.admin_pin = Some(pin.into()); self @@ -102,6 +111,7 @@ impl Nitrocli { stdout: &mut stdout, stderr: &mut stderr, is_tty: false, + path: self.path.clone(), admin_pin: self.admin_pin.clone(), user_pin: self.user_pin.clone(), new_admin_pin: self.new_admin_pin.clone(), diff --git a/src/tests/run.rs b/src/tests/run.rs index 33191d3..e4bbb28 100644 --- a/src/tests/run.rs +++ b/src/tests/run.rs @@ -5,8 +5,12 @@ use std::collections; use std::convert; +use std::convert::TryFrom as _; use std::convert::TryInto as _; +use std::fs; +use std::io::Write; use std::ops; +use std::os::unix::fs::OpenOptionsExt; use std::path; use super::*; @@ -277,3 +281,114 @@ fn connect_usb_path_model_wrong_serial(_model: nitrokey::Model) -> anyhow::Resul } Ok(()) } + +#[test] +fn extension() -> anyhow::Result<()> { + let ext_dir = tempfile::tempdir()?; + { + let mut ext = fs::OpenOptions::new() + .create(true) + .mode(0o755) + .write(true) + .open(ext_dir.path().join("nitrocli-ext"))?; + + ext.write_all( + br#"#!/usr/bin/env python +print("success") +"#, + )?; + } + + let path = ext_dir.path().as_os_str().to_os_string(); + let out = Nitrocli::new().path(path).handle(&["ext"])?; + assert_eq!(out, "success\n"); + Ok(()) +} + +#[test] +fn extension_failure() -> anyhow::Result<()> { + let ext_dir = tempfile::tempdir()?; + { + let mut ext = fs::OpenOptions::new() + .create(true) + .mode(0o755) + .write(true) + .open(ext_dir.path().join("nitrocli-ext"))?; + + ext.write_all( + br#"#!/usr/bin/env python +import sys +sys.exit(42); +"#, + )?; + } + + let path = ext_dir.path().as_os_str().to_os_string(); + let mut ncli = Nitrocli::new().path(path); + + let err = ncli.handle(&["ext"]).unwrap_err(); + // The extension is responsible for printing any error messages. + // Nitrocli is expected not to mess with them, including adding + // additional information. + if let Some(crate::DirectExitError(rc)) = err.downcast_ref::<crate::DirectExitError>() { + assert_eq!(*rc, 42) + } else { + panic!("encountered unexpected error: {:#}", err) + } + + let (rc, out, err) = ncli.run(&["ext"]); + assert_eq!(rc, 42); + assert_eq!(out, b""); + assert_eq!(err, b""); + Ok(()) +} + +#[test_device] +fn extension_arguments(model: nitrokey::Model) -> anyhow::Result<()> { + fn test<F>(model: nitrokey::Model, what: &str, args: &[&str], check: F) -> anyhow::Result<()> + where + F: FnOnce(&str) -> bool, + { + let ext_dir = tempfile::tempdir()?; + { + let mut ext = fs::OpenOptions::new() + .create(true) + .mode(0o755) + .write(true) + .open(ext_dir.path().join("nitrocli-ext"))?; + + ext.write_all(include_bytes!("extension_var_test.py"))?; + } + + let mut args = args.to_vec(); + args.append(&mut vec!["ext", what]); + + let path = ext_dir.path().as_os_str().to_os_string(); + let out = Nitrocli::new().model(model).path(path).handle(&args)?; + + assert!(check(&out), out); + Ok(()) + } + + test(model, "binary", &[], |out| { + path::Path::new(out) + .file_stem() + .unwrap() + .to_str() + .unwrap() + .trim() + .contains("nitrocli") + })?; + test(model, "model", &[], |out| { + out == args::DeviceModel::try_from(model).unwrap().to_string() + "\n" + })?; + test(model, "no-cache", &[], |out| out == "true\n")?; + test(model, "serial-numbers", &[], |out| out == "\n")?; + test(model, "verbosity", &[], |out| out == "0\n")?; + test(model, "verbosity", &["-v"], |out| out == "1\n")?; + test(model, "verbosity", &["-v", "--verbose"], |out| out == "2\n")?; + + // NITROCLI_USB_PATH should not be set, so the program errors out. + let _ = test(model, "usb-path", &[], |out| out == "\n").unwrap_err(); + Ok(()) +} |