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

Add tests for legacy Backend impl #1751

Merged
merged 11 commits into from
Sep 6, 2024
Merged

Add tests for legacy Backend impl #1751

merged 11 commits into from
Sep 6, 2024

Conversation

pkhry
Copy link
Contributor

@pkhry pkhry commented Sep 2, 2024

Description

As in the title. This PR adds tests for LegacyBackend implementation in subxt/backend.
Pr uses a mocked RpcClient implementation for providing expected responses.

Part of the work on #1677

@pkhry pkhry requested a review from a team as a code owner September 2, 2024 09:06

// For each key, return it + a future to get the result.
let iter = keys.into_iter().map(move |key| {
fn get_entry<T: Config>(
Copy link
Member

Choose a reason for hiding this comment

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

I don't follow what you fixed with this but I think the previous code would try to fetch all storage keys in one retry block i.e, all storage key calls needs to be successful or retry it.

Looks more efficient to try each storage entry individually as you do now but maybe some other issue with it that I don't follow?

Copy link
Contributor Author

@pkhry pkhry Sep 4, 2024

Choose a reason for hiding this comment

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

Let's say we tried to fetch 2 entries:

  • "A" and "B".
  • "A" returns an OK result
  • "B" returns an error
    The function would return Ok(streamOf(OK,Err)

So it just would not retry before this change

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yup ok, so previously we wrapped code in retry, but that code didn't actually do any async stuff and just returned a StreamOf. Now, we are retrying each individual async request to fetch a storage value. Good catch!

Copy link
Member

Choose a reason for hiding this comment

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

Cool, you could also create a separate PR for this if you want get it in ASAP :)

subxt/src/backend/mod.rs Outdated Show resolved Hide resolved
subxt/src/backend/mod.rs Outdated Show resolved Hide resolved
Copy link
Member

@niklasad1 niklasad1 left a comment

Choose a reason for hiding this comment

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

Nice work, looks promising.

I would like some more wrapper functions to make the code a little easier to read such as subscription instead of using mpsc::channel etc. It took my a while to understand why the channel was not used for pure method calls etc.

data: Arc<Mutex<Data<StorageEntry>>>,
}

impl RpcClientT for MockRpcClientStorage {
Copy link
Collaborator

@jsdw jsdw Sep 4, 2024

Choose a reason for hiding this comment

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

I wonder whether you can generalize this a little to then reuse in the UNstableBackend tests and generally for testing any RPC stuff, eg maybe we could have an interface like:

struct MockRpcClient { .. }

let mock_client = MockRpcClient::builder()
    // Mock methods
    .method(|name, params| {
        match name {
            // See methods and send back mock responses.
            "state_getStorage" => {
                Some(something_that_serializes_to_json)
            },
            // Return an error or None if we haven't mocked the method.
            // This will be turned into some error
            _ => {
                None
            }
        }
    })
    // Mock subscriptions
    .subscription(|name, params| {
        match name {
            "..." => {
                // Return some vec of serializable responses or something like that
                // which are then each sent as individual stream items. 
            }
        }
    })
    .build();

// `MockRpcClient` impls RpcClientT so can be used with Subxt backends etc.
subxt::rpc::RpcClient::new(mock_client);

// We can pull stuff into the closure to mock responses in order or whatever we like
fn mock_storage_responses() -> MockRpcClient {
    let mut mock_storage_responses = VecDeque::from_iter([
        mock_repsonse1, 
        mock_response2, 
        ..
    ]);

    MockRpcClient::builder()
        .method(move |name, params| {
            match name {
                "state_getStorage" => mock_storage_responses.pop_front(),
                _ => None
            }
        })
        .build()
} 

This might provide a nice sort of foundation to write a bunch of these sorts of tests

Copy link
Contributor Author

@pkhry pkhry Sep 4, 2024

Choose a reason for hiding this comment

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

i'm currently trying to see if it would work with unstable backend testing, although i'd prefer to make this change in the next PR

@pkhry pkhry requested review from niklasad1 and jsdw September 4, 2024 09:24
transaction_version: num,
other: HashMap::new(),
}
}
Copy link
Collaborator

@jsdw jsdw Sep 4, 2024

Choose a reason for hiding this comment

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

This is a good start! I think it would be good to try and make it super clear/simple if possible, eg something along the lines of:

#[tokio::test]
async fn storage_fetch_values() {
    // Setup
    let mock_data = [
        ("ID1", Ok("Data1".as_bytes())),
        ("ID2", Err(RpcError::DisconnectedWillReconnect("Reconnecting".to_string()))),
        ("ID2", Ok("Data2".as_bytes())),
        ("ID3", Err(RpcError::DisconnectedWillReconnect("Reconnecting".to_string()))),
        ("ID3", Ok("Data3".as_bytes())),
    ];
    let mock_client = mock_storage_client(mock_data.clone());

    // Test
    let backend: LegacyBackend<Conf> = LegacyBackend::builder().build(rpc_client);
    let response = backend
        .storage_fetch_values(
            mock_data.iter().map(|x| x.0.clone().into()).collect(),
            crate::utils::H256::random(),
        )
        .await
        .unwrap();

    let response = response
        .map(|x| x.unwrap())
        .collect::<Vec<StorageResponse>>()
        .await;
    let expected: Vec<_> = mock_data
        .iter()
        .map(|(key, value)| storage_response(key, value))
        .collect();
    assert_eq!(expected, response)
}

There mock_storage_client is a small function that wraps `MockRpcClient and takes some IntoIterator (in my example at least) to return appropriate storage entries

Copy link
Contributor Author

Choose a reason for hiding this comment

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

factored some of the common code in the last commit, let me know if it looks good to you

@pkhry pkhry requested a review from jsdw September 4, 2024 11:57
subxt/src/backend/mod.rs Outdated Show resolved Hide resolved
subxt/src/backend/mod.rs Outdated Show resolved Hide resolved
subxt/src/backend/mod.rs Outdated Show resolved Hide resolved
pkhry and others added 2 commits September 4, 2024 14:27
Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
@pkhry pkhry requested a review from niklasad1 September 4, 2024 12:27
subxt/src/backend/mod.rs Outdated Show resolved Hide resolved
Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
subxt/src/backend/mod.rs Outdated Show resolved Hide resolved
Copy link
Member

@niklasad1 niklasad1 left a comment

Choose a reason for hiding this comment

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

LGTM

Comment on lines +497 to +508
enum Conf {}
impl Config for Conf {
type Hash = crate::utils::H256;
type AccountId = crate::utils::AccountId32;
type Address = crate::utils::MultiAddress<Self::AccountId, ()>;
type Signature = crate::utils::MultiSignature;
type Hasher = crate::config::substrate::BlakeTwo256;
type Header = crate::config::substrate::SubstrateHeader<u32, Self::Hasher>;
type ExtrinsicParams = DefaultExtrinsicParams<Self>;

type AssetId = u32;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Prob worth a comment for why this is necessary instead of just importing SubstrateConfig or whatever :)

Copy link
Collaborator

@jsdw jsdw left a comment

Choose a reason for hiding this comment

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

The tests look much cleaner now; nice one!

I still would like to see a more generic MockRpcClient thing that I think might help to simplify some of the code around the tests, but can be tried out as part of a separate PR!

@pkhry pkhry merged commit b8735e5 into master Sep 6, 2024
13 checks passed
@pkhry pkhry deleted the phry/legacy-backend-testing branch September 6, 2024 09:26
@jsdw jsdw mentioned this pull request Oct 24, 2024
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.

3 participants