-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Testing the application #14
Comments
Thanks for bringing up this issue. I agree the current set of tests we have is not great and the overall approach to testing leaves a lot to be desired. Indeed, this crate looks promising and would help us test in an end-to-end fashion. I see that it has a lot of (transitive) dependencies (including
I also don't see too many options. I'd prefer a runtime based approach, though. Meaning we would detect the type(s) of stick(s) present and skip tests that are not supported. The That raises an interesting question on how we should treat cases where both a Pro and a Storage stick are present. Perhaps we can come up with a solution such as the following: #[test]
// Actually run the test on both devices, if present. That is, run it twice. If only one is present run on that one. If none, skip.
#[accept(Pro, Storage)]
fn hotp_no_pin() {
// ...
}
#[test]
// Only run on a storage device, if present. Skip the test otherwise.
#[accept(Storage)]
fn open_encryped_volume() {
// ...
} This is more pseudo code and idealistic thinking than anything else. I am not sure how feasible an attribute based implementation is. |
Which model did you request?
Just to be clear: The
From the In general, I think that there is still a lot of work to do on
Yes, that would be nice. |
Thanks for the clarification, Robin.
I asked for a Storage stick just because right now that is what the application uses and is designed for. But I mentioned that having a Pro device in addition to that would help in the near-term future. |
I’m using my Pro (v1) “in production” and my Storage (v2) for development and testing. So it would make sense for one of us to get a Pro for development some day. I can do some tests with my Pro, but I will not run the full test suite often. |
There may be another issue with respect to tests and that is that I think that even if the |
Currently, |
I think we can come pretty close to that (or make it even better). I just experimented with a procedural macro for a custom attribute. I have a crude prototype (not even worth showing :D) but I think we should be able to get to something like:
The main thing I wanted to verify here was that we can essentially emit a custom I'll also see if we can fudge the serialization issue with such a mechanism (essentially by just having a global lock that is grabbed for each function; I'd much rather have that as opposed to pushing the task of setting the thread count to the user, with god knows what happening if he/she forgets). |
Looks great! How do you choose which tests to execute? |
Basically all that happens is that we generate one or two wrappers around the provided test function. These are just ordinary Rust test functions, i.e., they will just appear and feel like a regular test function. That also means that you can pattern match on them when invoking them through Here is a more polished version: 9aa02e2
(ignore the test failures, they are due to missing synchronization) It works, but it does not yet account for a device not being present and skipping the test. There are probably a few more rough edges. My question to you is whether functionality like this is something you would want for the |
Yes, it would be great if |
Awesome! I verified that serialization of all tests is possible and have it implemented already. I will implement a bunch of tests on top of it, both on the |
I got a version of the (the full patch set can be found on the topic/nitrokey-test branch) A few unordered thoughts from my side:
|
I got a version of the `nitrokey` device tests converted to using the new `nitrokey-test` crate. With it, conceptually all you need to run the tests is `cargo test`. No more features, no more worrying about threads. Please have a look at e7f4b66 if you have time and let me know if you have suggestions @robinkrahl
(the full patch set can be found on the [topic/nitrokey-test](https://github.com/d-e-s-o/nitrocli/commits/topic/nitrokey-test) branch)
That sounds great! I’ll have a look at it once I finished the ongoing
implementation and documentation issues.
- I think if you decide to upgrade `nitrokey` to Rust 2018 we can export the `nitrokey-test::test_device` function as `nitrokey::test` (serde does something [similar](https://github.com/serde-rs/serde/blob/47e238aa13523fc98bfa540a9f0398c73ad52cf4/serde/src/lib.rs#L292))
The code on the next branch is already upgraded to Rust 2018.
- `nitrokey` should probably provide a proper error code when trying to connect to a device that is not present; `Unknown` seems too unspecific, in my opinion
Yes, that’s one point on my to-do list. I was considering returning an
Option<...> instead. What do you think about that? We do not receive
any error code from libnitrokey, so that might be more appropriate.
I am planning to release version 0.2.2 soon. Besides implementing
`Copy` for some types, do you have anything I should include? (I won’t
have time to look at `nitrokey-test` before that, so that will come with
the next release.)
|
I'll check that out. Thanks for the pointer!
Yeah, I think that makes sense. But, wouldn't that have to be considered a compatibility breaking change? Do you really plan to go with
I can't think of anything else right now, but I also haven't worked closely with the crate to be honest. |
Yeah, I think that makes sense. But, wouldn't that have to be considered a compatibility breaking change? Do you really plan to go with `0.2.2` in that case?
I’ll release 0.2.2. with the minor changes already published on the next
branch. 0.3.0 will follow soon, but there are some other breaking
changes I want to include: I’ll change `set_time` to `set_time(force:
bool)`. Also, I am considering not to re-export the public interface in
the main module and to rename the getter methods according to the style
guide.
|
Could you add tests that are executed if no device is connected (maybe if the function does not have any arguments)? That would be important for the Also, would it be possible to force |
Isn't that exactly what the existing
What test suites exactly are you referring to? Storage vs. Pro vs. No-device? Why have that concept to begin with if it is not needed? |
Yes. The point of having tests is that something might go wrong, even when connecting. Currently I can verify that the appropriate tests have been executed by checking the output, but I can’t express that I expect the Pro tests to be executed. Also it would improve the execution time by removing one command per test. Don’t get me wrong, these are no big issues for me. But it would be handy if there was an easy solution. |
So you want the test to run if the connection failed to begin with, because that's when no device is present. Yeah, that can be done.
I don't think we can have both, tests that detect availability of a device at runtime and ones that will fail if a device is not present. Well, we could better if there was an API to check for existence of a device of a particular model. But for lack of that we have to assume that a connection succeeds if and only if a device is present (though we could certainly request such a function). I prefer having tests that run without user configuration and test what ever can be tested, but I can see it being a trade off. |
I guess another possibility (and perhaps that's closer to what you were getting at) is to layer additional feature recognition on top of the detection logic. So what I have would be the default behavior, but if you run I think that should not be hard. |
Yeah, exactly. The current tests won’t be very meaningful – assert that the connection fails (unit test) if the connection failed (execution guard). But I try to have complete tests, and there might be more interesting test cases.
Yes, that’s what I was thinking about. I would prefer to have a run-time option (or an environment variable), but features would also be an option. |
Okay, I first want to release a new version of |
I am not sure it is a good idea to convert errors like you do here: https://github.com/d-e-s-o/nitrocli/blob/master/nitrokey/src/util.rs#L114 Wouldn't it be better to pass through the original error instead? Arguably, that's not the most significant problem here because the error does not contain too much additional information, but in general I don't think you should throw away the original. |
Also, here you probably want to preserve the original error code. Now I have no way of knowing what exactly went wrong when I get an |
As far as I remember, this is the only instance where we convert an internal error into a
Good point, I’ll change that. For the time being, you can use |
I actually already have a change that addresses that shortcoming, so don't worry about it. What's your preferred way to receive contributions? Just e-mail? |
I prefer patches via mail ( |
Here is an example how I’d write the tests: |
Thanks for looking into this topic! The problem I have is less with command execution and output retrieval. The problem is with how to determine which device to use etc. (basically what we discussed somewhere above). |
Maybe I am missing something, but I don’t see the problem with using The test mentioned above would be: #[test_device]
fn totp_no_pin(_: nitrokey::DeviceWrapper) {
} or better: #[test_device(DeviceWrapper)]
fn totp_no_pin() {
} |
I am not aware of that having been discussed. Yeah, that would work, but my goal was to support two devices being present. |
Do you want to support it for nitrocli in general or for the tests? I don’t think it makes sense to implement this functionality only for the tests – and if we would support it for nitrocli in general, we could pass the appropriate flags in the tests. Regarding the previous discussions: We were talking about the support of multiple devices earlier in this thread. I originally wanted to comment some more on that issue, but apparently I deleted it as I considered it to be off-topic. We have three strategies:
My preferred solution is the third, but we don’t have the library support to implement it. We can implement the second, but that requires some refactoring. I’d add top-level |
I'll respond to your questions in a bit in more detail, but let me first clarify that this statement was only meant for the testing situation (I implicitly assumed that because this issue is about testing, but we are now also touching on |
Basically I haven't really thought about
I am honestly not sure if supporting multiple devices from For Granted, if |
I think the code structure would be similar for both approaches: Either we have some kind of mutable global variable indicating the device type, or an additional argument for the command handlers in the |
Yeah, sounds good. I think the context logic I added with my last pull request will make this differentiation trivial. Do you already have |
I don’t have anything prepared. The OTP example was just intended as a starting point for further discussion. Just let me know what you are working on and what I could do. |
If you want you can add the Perhaps it's best to just make a list of open items and before anyone starts working on any we just compare(-the-assignee)-and-swap(-our-name) in there. What do you think? Shall we repurpose issue #40 for that? |
Okay, I’ll do that. But I think I’ll use
I noticed that we still have to know for which device we execute the test. Otherwise we can’t pass the appropriate flags to #[nitrokey_test::test]
fn test(model: nitrokey::DeviceModel) {
// execute `nitrocli --model <model>`
}
I’m still not sure whether we should call it PIN or password. PIN would be consistent with the user and admin PIN, but it is not managed by the smart card and not blocked after three retries (which for me is the main difference between a PIN and a password).
Good idea, but I cannot edit your issue. Can you edit my comments? Otherwise we could use a Github wiki page (if they still exist). |
👍
Okay. I was thinking along the lines of: fn test_impl(arg: String) {
...
}
#[nitrokey_test::test(nitrokey::Pro)]
fn pro_test() {
test_impl("--model=pro")
}
#[nitrokey_test::test(nitrokey::Storage)]
fn storage_test() {
test_impl("--model=storage")
} but given that
Yeah I thought about that, too. In my opinion the usage of user PIN/admin PIN is already a misnomer. From what I understand the entered string does not have to be a number (as in Personal Identification Number). I am not sure whether PIN evokes associations with the smart card per se. The other question (and perhaps I would make the answer to the above dependent on its answer) is whether to put this functionality below
Hm. Yeah, I can edit your comments. Okay, a wiki page is fine (should still be available)! Let me quickly create something. |
I've created https://github.com/d-e-s-o/nitrocli/wiki Let me know if there are problems with it. Improve at will. |
I looked at this proposal a bit more and to me it seems not like a really great model to work with. Basically, while the proposed That: #[nitrokey_test::test]
fn test(model: nitrokey::DeviceModel::Pro) {
// ...
} Isn't really valid Rust and while it does not matter much because the attribute macro can massage it to the right thing, it seems weird to me. The alternative is #[nitrokey_test::test(Pro)]
fn test() {
// ...
} but that doesn't really pair nicely with the aforementioned signature. However, I believe we can just use the trait IntoArg {
fn into_arg(self) -> &'static str;
}
impl IntoArg for nitrokey::Pro {
fn into_arg(self) -> &'static str {
"--model=pro"
}
}
impl IntoArg for nitrokey::Storage {
fn into_arg(self) -> &'static str {
"--model=storage"
}
}
impl IntoArg for nitrokey::DeviceWrapper {
fn into_arg(self) -> &'static str {
match self {
nitrokey::DeviceWrapper::Pro(x) => x.into_arg(),
nitrokey::DeviceWrapper::Storage(x) => x.into_arg(),
}
}
}
#[test_device]
fn foobar(device: nitrokey::DeviceWrapper) {
let args = ["nitrocli", device.into_arg(), "status"];
assert!(run(&args[..]).is_ok());
}
#[test_device]
fn foobarbaz(device: nitrokey::Storage) {
let args = ["nitrocli", device.into_arg(), "storage", "status"];
assert!(run(&args[..]).is_ok());
}
|
I shamelessly copied from your |
Could you elaborate on that? If it is about the interactive query, that’s why we have #27. Otherwise I don’t get the problem. |
Ups. Yes, issue #27 is what I am referring to. I must have paged that out already. Thanks for the reminder :) |
@robinkrahl , do you have an opinion on using regular expressions for verifying the output we produce from For example: 8b7c19f |
Looks good, especially as people might want to parse the |
Let me close this issue. I am regarding it as a discussion ground for the general approach to integration testing and we have settled on this, with the first tests having hit If we see the need for discussion of individual tests lets create a new issue. |
I think we should try to test the command-line interface using integration tests. We could either manually execute the command or use the
assert_cmd
crate.The most complex part is dealing with the different scenarios 1) no device present, 2) Nitrokey Pro present, 3) Nitrokey Storage present, and potentially 4) several devices present. For
nitrokey
, I defined features for the device types and prefixed every test with a conditional ignore, for example:I’m not really happy with this solution, but it was the best option I could think of. What are your thoughts on this issue?
The text was updated successfully, but these errors were encountered: