Skip to content
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

Improve cli args parsing in bench-tps #30307

Merged

Conversation

KirillLykov
Copy link
Contributor

@KirillLykov KirillLykov commented Feb 14, 2023

Problem

Parsing cli args is different from other places (due to exit, panic, println), it also makes it more difficult to test.
Added a basic unit test for cli.
This is preliminary work to migrate it to clap-v3, most of the things will be rewritten during migration to clap-v3

Summary of Changes

  • Rename parse function
  • Get rid of panics, exit and println
  • Add basic unit tests

@KirillLykov
Copy link
Contributor Author

KirillLykov commented Feb 14, 2023

Rust compiler shows warnings which looks weird for me:

error: unused imports: `read_keypair_file`, `super::*`, `write_keypair_file`
   --> bench-tps/src/cli.rs:559:9
    |
559 |         super::*,
    |         ^^^^^^^^
560 |         solana_sdk::signature::{read_keypair_file, write_keypair_file, Keypair, Signer},
    |                                 ^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^
    |
    = note: `-D unused-imports` implied by `-D warnings`
 
    Checking solana-validator v1.16.0 (/solana/validator)
    Checking solana-rpc-test v1.16.0 (/solana/rpc-test)
    Checking solana-client-test v1.16.0 (/solana/client-test)
error: function `make_tmp_path` is never used
   --> bench-tps/src/cli.rs:563:8
    |
563 |     fn make_tmp_path(name: &str) -> String {
    |        ^^^^^^^^^^^^^
    |
    = note: `-D dead-code` implied by `-D warnings`

because these imports and function are clearly used

@ilya-bobyr
Copy link
Contributor

Rust compiler shows warnings which looks weird for me:

[...]

because these imports and function are clearly used

This is because tests are not part of the normal compilation profile.
They are only compiled when the unit test binaries are generated.
It means, that this function:

    #[test]
    #[allow(clippy::cognitive_complexity)]
    fn test_cli_parse() {

is ignored by the compiler. The compiler will then see that

    fn make_tmp_path(name: &str) -> String {

is not called by anyone in the same module.
And as it has visibility that restricts it to the module, it can not be used by anybody else.

Adding #[cfg(test)] to the tests module definition makes the whole module only visible when building tests. And removes the warnings:

diff --git bench-tps/src/cli.rs bench-tps/src/cli.rs
index b6eb6..2956d 100644
--- bench-tps/src/cli.rs
+++ bench-tps/src/cli.rs
@@ -554,6 +554,7 @@ pub fn parse_args(matches: &ArgMatches) -> Result<Config, &'static str> {
     Ok(args)
 }

+#[cfg(test)]
 mod tests {
     use {
         super::*,

mod tests {
use {
super::*,
solana_sdk::signature::{read_keypair_file, write_keypair_file, Keypair, Signer},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, since you are using super::*, you don't need to reimport elements imported in the top-level module.

Suggested change
solana_sdk::signature::{read_keypair_file, write_keypair_file, Keypair, Signer},
solana_sdk::signature::{write_keypair_file, Signer},

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The evil of the wildcard imports is raising its ugly head

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grumble. This is now inconsistent with the pattern in nearly every other test module in the repo, where we use super::*

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you clarify what exactly is inconsistent?
After using super::* it looks similar to the other tests that use super::*.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this one isn't using super::*

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I got confused.
Somehow, I saw this change as using super::*.
It is even in the email that GitHub sent:
image

GitHub reviewer experience is a rollercoaster %)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lol, yeah, it's weird it's not showing that preview as outdated.
Oh well. I'm not going to fight on this one anymore anyway.

bench-tps/src/cli.rs Outdated Show resolved Hide resolved
mod tests {
use {
super::*,
solana_sdk::signature::{read_keypair_file, write_keypair_file, Keypair, Signer},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The evil of the wildcard imports is raising its ugly head

bench-tps/src/cli.rs Outdated Show resolved Hide resolved
bench-tps/src/main.rs Outdated Show resolved Hide resolved
@@ -35,12 +35,14 @@ impl Default for ExternalClientType {
}
}

#[derive(PartialEq, Debug)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor

When deriving PartialEq, you normally also want to derive Eq.
Only a rather specific set of types (such as floating point numbers) implement only PartialEq without Eq.
But as the compiler can not check it, it is up to the person writing the code to make this call.

Here are the details on when absence of Eq matters: https://doc.rust-lang.org/book/appendix-03-derivable-traits.html?highlight=partialeq#partialeq-and-eq-for-equality-comparisons

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here the problem with Eq is that Keypair is not implementing it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why it is the case.
Keypair is just a PublicKey and a SecretKey.
Seems like it might be intentional, as PublicKey does implement it.
Not sure why SecretKey would not.
I've opened an issue, with a few questions, in the library repo.

But you can still add Eq to the other places where you are adding PartialEq.
At least, I personally, try to do it by default.
When someone needs it, they need to go and figure out if Eq was omitted by accident or not. Which is generally more complex, compared to adding 3 characters when the code is written :))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized that PartialEq was implemented for Keypair in https://github.com/solana-labs/solana/blob/master/sdk/src/signer/keypair.rs#L107-L114
Seems like this is exactly the case when PartialEq was implemented, without adding Eq in there.

Here is a PR to fix it: #30381

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like leaving as it is

Copy link
Contributor

@ilya-bobyr ilya-bobyr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay to the unit tests and proper error handling!

@@ -35,12 +35,14 @@ impl Default for ExternalClientType {
}
}

#[derive(PartialEq, Debug)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why it is the case.
Keypair is just a PublicKey and a SecretKey.
Seems like it might be intentional, as PublicKey does implement it.
Not sure why SecretKey would not.
I've opened an issue, with a few questions, in the library repo.

But you can still add Eq to the other places where you are adding PartialEq.
At least, I personally, try to do it by default.
When someone needs it, they need to go and figure out if Eq was omitted by accident or not. Which is generally more complex, compared to adding 3 characters when the code is written :))

Comment on lines 500 to 506
let parsed_data_size = data_size
.to_string()
.parse()
.map_err(|_| "Can't parse padded instruction data size")?;
args.instruction_padding_config = Some(InstructionPaddingConfig {
program_id,
data_size: data_size
.to_string()
.parse()
.expect("Can't parse padded instruction data size"),
data_size: parsed_data_size,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<minor-style>

I think it is totaly fine to shadow data_size here:

        let data_size = data_size
            .to_string()
            .parse()
            .map_err(|_| "Can't parse padded instruction data size")?;
        args.instruction_padding_config = Some(InstructionPaddingConfig {
            program_id,
            data_size,
        });

</minor-style>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, .to_string() is unnecessary here.
.parse() works just fine on a &str.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@ilya-bobyr ilya-bobyr mentioned this pull request Feb 17, 2023
@KirillLykov KirillLykov force-pushed the refactor_parse_cli_bench_tps branch from b59c1a0 to 7a7105c Compare February 18, 2023 10:53
@KirillLykov KirillLykov force-pushed the refactor_parse_cli_bench_tps branch from e542f39 to 5542876 Compare February 23, 2023 09:42
dmakarov
dmakarov previously approved these changes Feb 23, 2023
CriesofCarrots
CriesofCarrots previously approved these changes Feb 23, 2023
mod tests {
use {
super::*,
solana_sdk::signature::{read_keypair_file, write_keypair_file, Keypair, Signer},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grumble. This is now inconsistent with the pattern in nearly every other test module in the repo, where we use super::*

@KirillLykov KirillLykov dismissed stale reviews from CriesofCarrots and dmakarov via fcad6aa February 23, 2023 17:16
@KirillLykov KirillLykov force-pushed the refactor_parse_cli_bench_tps branch from 5542876 to fcad6aa Compare February 23, 2023 17:16
dmakarov
dmakarov previously approved these changes Feb 23, 2023
ilya-bobyr
ilya-bobyr previously approved these changes Feb 23, 2023
Copy link
Contributor

@ilya-bobyr ilya-bobyr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor comments, but looks good to me otherwise :)

.parse()
.expect("can't parse tpu_connection_pool_size");
.parse::<usize>()
.map_err(|_| "can't parse tpu_connection_pool_size")?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the user standpoint, the argument name is tpu-connection-pool-size, with dashes, right?
Here and in a few other places.
Technically, this issue was there before. But, maybe, you would want to fix it as well, as you are doing this cleanup.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is correct, so I've fixed it.
The plan is to eliminate this manual parsing by using clap-v3 with derive feature

@@ -486,23 +485,25 @@ pub fn extract_args(matches: &ArgMatches) -> Config {
}

if let Some(v) = matches.value_of("target_lamports_per_signature") {
args.target_lamports_per_signature = v.to_string().parse().expect("can't parse lamports");
args.target_lamports_per_signature =
v.to_string().parse().map_err(|_| "can't parse lamports")?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
v.to_string().parse().map_err(|_| "can't parse lamports")?;
v.to_string().parse().map_err(|_| "can't parse target-lamports-per-signature")?;

Comment on lines 493 to 495
args.target_node = matches
.value_of("target_node")
.map(|target_str| target_str.parse().unwrap());
.map(|target_str| target_str.parse::<Pubkey>().unwrap());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still panicking. Here is how to fix it, to make it similar the other cases:

Suggested change
args.target_node = matches
.value_of("target_node")
.map(|target_str| target_str.parse().unwrap());
.map(|target_str| target_str.parse::<Pubkey>().unwrap());
args.target_node = matches
.value_of("target_node")
.map(|target_str| target_str.parse::<Pubkey>())
.transpose()
.map_err(|_| "Failed to parse target-node")?;

transpose() docs


if let Some(v) = matches.value_of("num_lamports_per_account") {
args.num_lamports_per_account = v.to_string().parse().expect("can't parse lamports");
args.num_lamports_per_account =
v.to_string().parse().map_err(|_| "can't parse lamports")?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
v.to_string().parse().map_err(|_| "can't parse lamports")?;
v.to_string().parse().map_err(|_| "can't parse num-lamports-per-account")?;

}

if let Some(t) = matches.value_of("target_slots_per_epoch") {
args.target_slots_per_epoch = t
.to_string()
.parse()
.expect("can't parse target slots per epoch");
.map_err(|_| "can't parse target slots per epoch")?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.map_err(|_| "can't parse target slots per epoch")?;
.map_err(|_| "can't parse target-slots-per-epoch")?;

);
let parsed_num_conflict_groups = num_conflict_groups
.parse()
.map_err(|_| "Can't parse conflict groups")?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.map_err(|_| "Can't parse conflict groups")?;
.map_err(|_| "Can't parse num-conflict-groups")?;

exit(1)
});
args.bind_address =
solana_net_utils::parse_host(addr).map_err(|_| "Failed to parse bind_address")?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
solana_net_utils::parse_host(addr).map_err(|_| "Failed to parse bind_address")?;
solana_net_utils::parse_host(addr).map_err(|_| "Failed to parse bind-address")?;

@KirillLykov KirillLykov dismissed stale reviews from ilya-bobyr and dmakarov via 1c35fe9 February 24, 2023 12:45
@KirillLykov KirillLykov force-pushed the refactor_parse_cli_bench_tps branch from 1c35fe9 to df748f2 Compare February 24, 2023 12:46
@KirillLykov KirillLykov merged commit 2c37763 into solana-labs:master Feb 24, 2023
nickfrosty pushed a commit to nickfrosty/solana that referenced this pull request Mar 12, 2023
* Improve cli args parsing in bench-tps

* Rename parse funciton
* Get rid of panics, exit and println
* Add basic unit tests

* add allow dead_code and unused_imports for cli tests

* simplify code by using map_err instead of macro

* simplify data_size parsing

* use tempfile crate to create temp keypair files

* fix bug with reading client_id from config by default, re-added client_id tests

* minor fix of an error message

* add Eq to bench_tps::cli::Congig

* some minor error messages corrections
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants