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

Fix multi block argument and add some unit tests #14

Merged
merged 6 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,14 @@ Dataset-specific Options:


Block specification syntax
- can use numbers --blocks 5000 6000 7000
- can use numbers --blocks 5000
- can use numbers list (use "") --blocks "5000 6000 7000"
- can use ranges --blocks 12M:13M 15M:16M
- numbers can contain { _ . K M B } 5_000 5K 15M 15.5M
- omiting range end means latest 15.5M: == 15.5M:latest
- omitting range start means 0 :700 == 0:700
- minus on start means minus end -1000:7000 == 6000:7000
- plus sign on end means plus start 15M:+1000 == 15M:15.001K
- mix formats "15M:+1 1000:1002 -3:1b 2000"
```

2 changes: 1 addition & 1 deletion crates/cli/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub struct Args {

/// Block numbers, see syntax below
#[arg(short, long, allow_hyphen_values(true), help_heading = "Content Options")]
pub blocks: Option<Vec<String>>,
pub blocks: Option<String>,

/// Transaction hashes, see syntax below
#[arg(
Expand Down
270 changes: 255 additions & 15 deletions crates/cli/src/parse/blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,26 +50,30 @@ pub(crate) async fn get_default_block_chunks(
args: &Args,
provider: Arc<Provider<Http>>,
) -> Result<Vec<Chunk>, ParseError> {
let block_chunks = parse_block_inputs(&vec!["0:latest".to_string()], &provider).await?;
let block_chunks = parse_block_inputs(&String::from(r"0:latest"), &provider).await?;
postprocess_block_chunks(block_chunks, args, provider).await
}

/// parse block numbers to freeze
async fn parse_block_inputs(
inputs: &Vec<String>,
provider: &Provider<Http>,
) -> Result<Vec<BlockChunk>, ParseError> {
match inputs.len() {
async fn parse_block_inputs<P>(
inputs: &str,
provider: &Provider<P>,
) -> Result<Vec<BlockChunk>, ParseError>
where
P: JsonRpcClient,
{
let parts: Vec<&str> = inputs.split(' ').collect();
match parts.len() {
1 => {
let first_input = inputs.get(0).ok_or_else(|| {
let first_input = parts.get(0).ok_or_else(|| {
ParseError::ParseError("Failed to get the first input".to_string())
})?;
parse_block_token(first_input, true, provider).await.map(|x| vec![x])
}
_ => {
let mut chunks = Vec::new();
for input in inputs {
chunks.push(parse_block_token(input, false, provider).await?);
for part in parts {
chunks.push(parse_block_token(part, false, provider).await?);
}
Ok(chunks)
}
Expand All @@ -82,12 +86,16 @@ enum RangePosition {
None,
}

async fn parse_block_token(
async fn parse_block_token<P>(
s: &str,
as_range: bool,
provider: &Provider<Http>,
) -> Result<BlockChunk, ParseError> {
provider: &Provider<P>,
) -> Result<BlockChunk, ParseError>
where
P: JsonRpcClient,
{
let s = s.replace('_', "");

let parts: Vec<&str> = s.split(':').collect();
match parts.as_slice() {
[block_ref] => {
Expand Down Expand Up @@ -143,11 +151,14 @@ async fn parse_block_token(
}
}

async fn parse_block_number(
async fn parse_block_number<P>(
block_ref: &str,
range_position: RangePosition,
provider: &Provider<Http>,
) -> Result<u64, ParseError> {
provider: &Provider<P>,
) -> Result<u64, ParseError>
where
P: JsonRpcClient,
{
match (block_ref, range_position) {
("latest", _) => provider.get_block_number().await.map(|n| n.as_u64()).map_err(|_e| {
ParseError::ParseError("Error retrieving latest block number".to_string())
Expand Down Expand Up @@ -209,3 +220,232 @@ async fn apply_reorg_buffer(
}
}
}

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

enum BlockTokenTest<'a> {
WithoutMock((&'a str, BlockChunk)), // Token | Expected
WithMock((&'a str, BlockChunk, u64)), // Token | Expected | Mock Block Response
}

async fn block_token_test_helper(tests: Vec<(BlockTokenTest<'_>, bool)>) {
let (provider, mock) = Provider::mocked();
for (test, res) in tests {
match test {
BlockTokenTest::WithMock((token, expected, latest)) => {
mock.push(U64::from(latest)).unwrap();
assert_eq!(block_token_test_executor(&token, expected, &provider).await, res);
}
BlockTokenTest::WithoutMock((token, expected)) => {
assert_eq!(block_token_test_executor(&token, expected, &provider).await, res);
}
}
}
}

async fn block_token_test_executor<P>(
token: &str,
expected: BlockChunk,
provider: &Provider<P>,
) -> bool
where
P: JsonRpcClient,
{
match expected {
BlockChunk::Numbers(expected_block_numbers) => {
let block_chunks = parse_block_token(token, false, &provider).await.unwrap();
assert!(matches!(block_chunks, BlockChunk::Numbers { .. }));
let BlockChunk::Numbers(block_numbers) = block_chunks else {
panic!("Unexpected shape")
};
return block_numbers == expected_block_numbers
}
BlockChunk::Range(expected_range_start, expected_range_end) => {
let block_chunks = parse_block_token(token, true, &provider).await.unwrap();
assert!(matches!(block_chunks, BlockChunk::Range { .. }));
let BlockChunk::Range(range_start, range_end) = block_chunks else {
panic!("Unexpected shape")
};
return expected_range_start == range_start && expected_range_end == range_end
}
}
}

enum BlockInputTest<'a> {
WithoutMock((&'a String, Vec<BlockChunk>)), // Token | Expected
WithMock((&'a String, Vec<BlockChunk>, u64)), // Token | Expected | Mock Block Response
}

async fn block_input_test_helper(tests: Vec<(BlockInputTest<'_>, bool)>) {
let (provider, mock) = Provider::mocked();
for (test, res) in tests {
match test {
BlockInputTest::WithMock((inputs, expected, latest)) => {
mock.push(U64::from(latest)).unwrap();
assert_eq!(block_input_test_executor(&inputs, expected, &provider).await, res);
}
BlockInputTest::WithoutMock((inputs, expected)) => {
assert_eq!(block_input_test_executor(&inputs, expected, &provider).await, res);
}
}
}
}

async fn block_input_test_executor<P>(
inputs: &String,
expected: Vec<BlockChunk>,
provider: &Provider<P>,
) -> bool
where
P: JsonRpcClient,
{
let block_chunks = parse_block_inputs(inputs, &provider).await.unwrap();
assert_eq!(block_chunks.len(), expected.len());
for (i, block_chunk) in block_chunks.iter().enumerate() {
let expected_chunk = &expected[i];
match expected_chunk {
BlockChunk::Numbers(expected_block_numbers) => {
assert!(matches!(block_chunk, BlockChunk::Numbers { .. }));
let BlockChunk::Numbers(block_numbers) = block_chunk else {
panic!("Unexpected shape")
};
if expected_block_numbers != block_numbers {
return false
}
}
BlockChunk::Range(expected_range_start, expected_range_end) => {
assert!(matches!(block_chunk, BlockChunk::Range { .. }));
let BlockChunk::Range(range_start, range_end) = block_chunk else {
panic!("Unexpected shape")
};
if expected_range_start != range_start || expected_range_end != range_end {
return false
}
}
}
}
return true
}

enum BlockNumberTest<'a> {
WithoutMock((&'a str, RangePosition, u64)),
WithMock((&'a str, RangePosition, u64, u64)),
}

async fn block_number_test_helper(tests: Vec<(BlockNumberTest<'_>, bool)>) {
let (provider, mock) = Provider::mocked();
for (test, res) in tests {
match test {
BlockNumberTest::WithMock((block_ref, range_position, expected, latest)) => {
mock.push(U64::from(latest)).unwrap();
assert_eq!(
block_number_test_executor(&block_ref, range_position, expected, &provider)
.await,
res
);
}
BlockNumberTest::WithoutMock((block_ref, range_position, expected)) => {
assert_eq!(
block_number_test_executor(&block_ref, range_position, expected, &provider)
.await,
res
);
}
}
}
}

async fn block_number_test_executor<P>(
block_ref: &str,
range_position: RangePosition,
expected: u64,
provider: &Provider<P>,
) -> bool
where
P: JsonRpcClient,
{
let block_number = parse_block_number(block_ref, range_position, &provider).await.unwrap();
return block_number == expected
}

#[tokio::test]
async fn block_token_parsing() {
// Ranges
let tests: Vec<(BlockTokenTest<'_>, bool)> = vec![
// Range Type
(BlockTokenTest::WithoutMock((r"1:2", BlockChunk::Range(1, 2))), true), /* Single block range */
(BlockTokenTest::WithoutMock((r"0:2", BlockChunk::Range(0, 2))), true), /* Implicit start */
(BlockTokenTest::WithoutMock((r"-10:100", BlockChunk::Range(90, 100))), true), /* Relative negative */
(BlockTokenTest::WithoutMock((r"10:+100", BlockChunk::Range(10, 110))), true), /* Relative positive */
(BlockTokenTest::WithMock((r"1:latest", BlockChunk::Range(1, 12), 12)), true), /* Explicit latest */
(BlockTokenTest::WithMock((r"1:", BlockChunk::Range(1, 12), 12)), true), /* Implicit latest */
// Number type
(BlockTokenTest::WithoutMock((r"1", BlockChunk::Numbers(vec![1]))), true), /* Single block */
];
block_token_test_helper(tests).await;
}

#[tokio::test]
async fn block_inputs_parsing() {
// Ranges
let block_inputs_single = String::from(r"1:2");
let block_inputs_multiple = String::from(r"1 2");
let block_inputs_latest = String::from(r"1:latest");
let block_inputs_multiple_complex = String::from(r"15M:+1 1000:1002 -3:1b 2000");
let tests: Vec<(BlockInputTest<'_>, bool)> = vec![
// Range Type
(
BlockInputTest::WithoutMock((&block_inputs_single, vec![BlockChunk::Range(1, 2)])),
true,
), // Single input
(
BlockInputTest::WithoutMock((
&block_inputs_multiple,
vec![BlockChunk::Numbers(vec![1]), BlockChunk::Numbers(vec![2])],
)),
true,
), // Multi input
(
BlockInputTest::WithMock((
&block_inputs_latest,
vec![BlockChunk::Range(1, 12)],
12,
)),
true,
), // Single input latest
(
BlockInputTest::WithoutMock((
&block_inputs_multiple_complex,
vec![
BlockChunk::Numbers(vec![15000000, 15000001]),
BlockChunk::Numbers(vec![1000, 1001, 1002]),
BlockChunk::Numbers(vec![999999997, 999999998, 999999999, 1000000000]),
BlockChunk::Numbers(vec![2000]),
],
)),
true,
), // Multi input complex
];
block_input_test_helper(tests).await;
}

#[tokio::test]
async fn block_number_parsing() {
// Ranges
let tests: Vec<(BlockNumberTest<'_>, bool)> = vec![
(BlockNumberTest::WithoutMock((r"1", RangePosition::None, 1)), true), // Integer
(BlockNumberTest::WithMock((r"latest", RangePosition::None, 12, 12)), true), /* Lastest block */
(BlockNumberTest::WithoutMock((r"", RangePosition::First, 0)), true), // First block
(BlockNumberTest::WithMock((r"", RangePosition::Last, 12, 12)), true), // Last block
(BlockNumberTest::WithoutMock((r"1B", RangePosition::None, 1000000000)), true), // B
(BlockNumberTest::WithoutMock((r"1M", RangePosition::None, 1000000)), true), // M
(BlockNumberTest::WithoutMock((r"1K", RangePosition::None, 1000)), true), // K
(BlockNumberTest::WithoutMock((r"1b", RangePosition::None, 1000000000)), true), // b
(BlockNumberTest::WithoutMock((r"1m", RangePosition::None, 1000000)), true), // m
(BlockNumberTest::WithoutMock((r"1k", RangePosition::None, 1000)), true), // k
];
block_number_test_helper(tests).await;
}
}
2 changes: 1 addition & 1 deletion crates/python/src/collect_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ use cryo_freeze::collect;
pub fn _collect(
py: Python<'_>,
datatype: String,
blocks: Option<Vec<String>>,
blocks: Option<String>,
txs: Option<Vec<String>>,
align: bool,
reorg_buffer: u64,
Expand Down
2 changes: 1 addition & 1 deletion crates/python/src/freeze_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ use cryo_cli::{run, Args};
pub fn _freeze(
py: Python<'_>,
datatype: Vec<String>,
blocks: Option<Vec<String>>,
blocks: Option<String>,
txs: Option<Vec<String>>,
align: bool,
reorg_buffer: u64,
Expand Down
Loading