Merge branch 'master' of github.com:ethcore/parity into executive_tests
This commit is contained in:
commit
e1b841b526
@ -16,6 +16,7 @@
|
||||
|
||||
//! Test client.
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrder};
|
||||
use util::*;
|
||||
use transaction::{Transaction, LocalizedTransaction, SignedTransaction, Action};
|
||||
use blockchain::TreeRoute;
|
||||
@ -54,6 +55,8 @@ pub struct TestBlockChainClient {
|
||||
pub execution_result: RwLock<Option<Executed>>,
|
||||
/// Transaction receipts.
|
||||
pub receipts: RwLock<HashMap<TransactionId, LocalizedReceipt>>,
|
||||
/// Block queue size.
|
||||
pub queue_size: AtomicUsize,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -90,6 +93,7 @@ impl TestBlockChainClient {
|
||||
code: RwLock::new(HashMap::new()),
|
||||
execution_result: RwLock::new(None),
|
||||
receipts: RwLock::new(HashMap::new()),
|
||||
queue_size: AtomicUsize::new(0),
|
||||
};
|
||||
client.add_blocks(1, EachBlockWith::Nothing); // add genesis block
|
||||
client.genesis_hash = client.last_hash.read().unwrap().clone();
|
||||
@ -121,6 +125,11 @@ impl TestBlockChainClient {
|
||||
self.storage.write().unwrap().insert((address, position), value);
|
||||
}
|
||||
|
||||
/// Set block queue size for testing
|
||||
pub fn set_queue_size(&self, size: usize) {
|
||||
self.queue_size.store(size, AtomicOrder::Relaxed);
|
||||
}
|
||||
|
||||
/// Add blocks to test client.
|
||||
pub fn add_blocks(&self, count: usize, with: EachBlockWith) {
|
||||
let len = self.numbers.read().unwrap().len();
|
||||
@ -383,7 +392,7 @@ impl BlockChainClient for TestBlockChainClient {
|
||||
|
||||
fn queue_info(&self) -> BlockQueueInfo {
|
||||
BlockQueueInfo {
|
||||
verified_queue_size: 0,
|
||||
verified_queue_size: self.queue_size.load(AtomicOrder::Relaxed),
|
||||
unverified_queue_size: 0,
|
||||
verifying_queue_size: 0,
|
||||
max_queue_size: 0,
|
||||
|
@ -137,6 +137,7 @@ impl InstructionInfo {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
||||
/// Return details about specific instruction
|
||||
pub fn get_info (instruction: Instruction) -> InstructionInfo {
|
||||
match instruction {
|
||||
@ -301,7 +302,7 @@ pub const EXP: Instruction = 0x0a;
|
||||
pub const SIGNEXTEND: Instruction = 0x0b;
|
||||
|
||||
/// less-than comparision
|
||||
pub const LT: Instruction = 0x10;
|
||||
pub const LT: Instruction = 0x10;
|
||||
/// greater-than comparision
|
||||
pub const GT: Instruction = 0x11;
|
||||
/// signed less-than comparision
|
||||
@ -324,10 +325,10 @@ pub const NOT: Instruction = 0x19;
|
||||
pub const BYTE: Instruction = 0x1a;
|
||||
|
||||
/// compute SHA3-256 hash
|
||||
pub const SHA3: Instruction = 0x20;
|
||||
pub const SHA3: Instruction = 0x20;
|
||||
|
||||
/// get address of currently executing account
|
||||
pub const ADDRESS: Instruction = 0x30;
|
||||
pub const ADDRESS: Instruction = 0x30;
|
||||
/// get balance of the given account
|
||||
pub const BALANCE: Instruction = 0x31;
|
||||
/// get execution origination address
|
||||
@ -367,7 +368,7 @@ pub const DIFFICULTY: Instruction = 0x44;
|
||||
pub const GASLIMIT: Instruction = 0x45;
|
||||
|
||||
/// remove item from stack
|
||||
pub const POP: Instruction = 0x50;
|
||||
pub const POP: Instruction = 0x50;
|
||||
/// load word from memory
|
||||
pub const MLOAD: Instruction = 0x51;
|
||||
/// save word to memory
|
||||
@ -392,7 +393,7 @@ pub const GAS: Instruction = 0x5a;
|
||||
pub const JUMPDEST: Instruction = 0x5b;
|
||||
|
||||
/// place 1 byte item on stack
|
||||
pub const PUSH1: Instruction = 0x60;
|
||||
pub const PUSH1: Instruction = 0x60;
|
||||
/// place 2 byte item on stack
|
||||
pub const PUSH2: Instruction = 0x61;
|
||||
/// place 3 byte item on stack
|
||||
@ -457,7 +458,7 @@ pub const PUSH31: Instruction = 0x7e;
|
||||
pub const PUSH32: Instruction = 0x7f;
|
||||
|
||||
/// copies the highest item in the stack to the top of the stack
|
||||
pub const DUP1: Instruction = 0x80;
|
||||
pub const DUP1: Instruction = 0x80;
|
||||
/// copies the second highest item in the stack to the top of the stack
|
||||
pub const DUP2: Instruction = 0x81;
|
||||
/// copies the third highest item in the stack to the top of the stack
|
||||
@ -490,7 +491,7 @@ pub const DUP15: Instruction = 0x8e;
|
||||
pub const DUP16: Instruction = 0x8f;
|
||||
|
||||
/// swaps the highest and second highest value on the stack
|
||||
pub const SWAP1: Instruction = 0x90;
|
||||
pub const SWAP1: Instruction = 0x90;
|
||||
/// swaps the highest and third highest value on the stack
|
||||
pub const SWAP2: Instruction = 0x91;
|
||||
/// swaps the highest and 4th highest value on the stack
|
||||
@ -523,7 +524,7 @@ pub const SWAP15: Instruction = 0x9e;
|
||||
pub const SWAP16: Instruction = 0x9f;
|
||||
|
||||
/// Makes a log entry; no topics.
|
||||
pub const LOG0: Instruction = 0xa0;
|
||||
pub const LOG0: Instruction = 0xa0;
|
||||
/// Makes a log entry; 1 topic.
|
||||
pub const LOG1: Instruction = 0xa1;
|
||||
/// Makes a log entry; 2 topics.
|
||||
@ -536,7 +537,7 @@ pub const LOG4: Instruction = 0xa4;
|
||||
pub const MAX_NO_OF_TOPICS : usize = 4;
|
||||
|
||||
/// create a new account with associated code
|
||||
pub const CREATE: Instruction = 0xf0;
|
||||
pub const CREATE: Instruction = 0xf0;
|
||||
/// message-call into an account
|
||||
pub const CALL: Instruction = 0xf1;
|
||||
/// message-call with another account's code only
|
||||
@ -546,5 +547,5 @@ pub const RETURN: Instruction = 0xf3;
|
||||
/// like CALLCODE but keeps caller's value and sender
|
||||
pub const DELEGATECALL: Instruction = 0xf4;
|
||||
/// halt execution and register account for later deletion
|
||||
pub const SUICIDE: Instruction = 0xff;
|
||||
pub const SUICIDE: Instruction = 0xff;
|
||||
|
||||
|
14
fmt.sh
Executable file
14
fmt.sh
Executable file
@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
RUSTFMT="rustfmt --write-mode overwrite"
|
||||
|
||||
$RUSTFMT ./ethash/src/lib.rs
|
||||
$RUSTFMT ./ethcore/src/lib.rs
|
||||
$RUSTFMT ./evmjit/src/lib.rs
|
||||
$RUSTFMT ./json/src/lib.rs
|
||||
$RUSTFMT ./miner/src/lib.rs
|
||||
$RUSTFMT ./parity/main.rs
|
||||
$RUSTFMT ./rpc/src/lib.rs
|
||||
$RUSTFMT ./sync/src/lib.rs
|
||||
$RUSTFMT ./util/src/lib.rs
|
||||
|
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
PARITY_DEB_URL=https://github.com/ethcore/parity/releases/download/v1.0.0-rc1/parity_linux_1.0.0.rc1-0_amd64.deb
|
||||
PARITY_DEB_URL=https://github.com/ethcore/parity/releases/download/v1.0.0/parity_linux_1.0.0-0_amd64.deb
|
||||
|
||||
|
||||
function run_installer()
|
||||
@ -435,13 +435,8 @@ function run_installer()
|
||||
echo
|
||||
|
||||
info "Installing parity"
|
||||
if [[ $isEth == true ]]
|
||||
then
|
||||
brew reinstall parity
|
||||
else
|
||||
brew install parity
|
||||
brew linkapps parity
|
||||
fi
|
||||
brew reinstall parity
|
||||
brew linkapps parity
|
||||
echo
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,6 @@ pub struct Miner {
|
||||
gas_floor_target: RwLock<U256>,
|
||||
author: RwLock<Address>,
|
||||
extra_data: RwLock<Bytes>,
|
||||
|
||||
}
|
||||
|
||||
impl Default for Miner {
|
||||
@ -166,28 +165,32 @@ impl MinerService for Miner {
|
||||
}
|
||||
|
||||
fn update_sealing(&self, chain: &BlockChainClient) {
|
||||
let should_disable_sealing = {
|
||||
if self.sealing_enabled.load(atomic::Ordering::Relaxed) {
|
||||
let current_no = chain.chain_info().best_block_number;
|
||||
let last_request = self.sealing_block_last_request.lock().unwrap();
|
||||
let is_greater = current_no > *last_request;
|
||||
is_greater && current_no - *last_request > SEALING_TIMEOUT_IN_BLOCKS
|
||||
};
|
||||
let last_request = *self.sealing_block_last_request.lock().unwrap();
|
||||
let should_disable_sealing = current_no > last_request && current_no - last_request > SEALING_TIMEOUT_IN_BLOCKS;
|
||||
|
||||
if should_disable_sealing {
|
||||
self.sealing_enabled.store(false, atomic::Ordering::Relaxed);
|
||||
*self.sealing_block.lock().unwrap() = None;
|
||||
} else if self.sealing_enabled.load(atomic::Ordering::Relaxed) {
|
||||
self.prepare_sealing(chain);
|
||||
if should_disable_sealing {
|
||||
trace!(target: "miner", "Miner sleeping (current {}, last {})", current_no, last_request);
|
||||
self.sealing_enabled.store(false, atomic::Ordering::Relaxed);
|
||||
*self.sealing_block.lock().unwrap() = None;
|
||||
} else {
|
||||
self.prepare_sealing(chain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sealing_block(&self, chain: &BlockChainClient) -> &Mutex<Option<ClosedBlock>> {
|
||||
if self.sealing_block.lock().unwrap().is_none() {
|
||||
self.sealing_enabled.store(true, atomic::Ordering::Relaxed);
|
||||
|
||||
self.prepare_sealing(chain);
|
||||
}
|
||||
*self.sealing_block_last_request.lock().unwrap() = chain.chain_info().best_block_number;
|
||||
let mut sealing_block_last_request = self.sealing_block_last_request.lock().unwrap();
|
||||
let best_number = chain.chain_info().best_block_number;
|
||||
if *sealing_block_last_request != best_number {
|
||||
trace!(target: "miner", "Miner received request (was {}, now {}) - waking up.", *sealing_block_last_request, best_number);
|
||||
*sealing_block_last_request = best_number;
|
||||
}
|
||||
&self.sealing_block
|
||||
}
|
||||
|
||||
|
@ -197,6 +197,8 @@ impl<C, S, A, M, EM> EthClient<C, S, A, M, EM>
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_QUEUE_SIZE_TO_MINE_ON: usize = 4; // because uncles go back 6.
|
||||
|
||||
impl<C, S, A, M, EM> Eth for EthClient<C, S, A, M, EM>
|
||||
where C: BlockChainClient + 'static,
|
||||
S: SyncProvider + 'static,
|
||||
@ -398,10 +400,12 @@ impl<C, S, A, M, EM> Eth for EthClient<C, S, A, M, EM>
|
||||
match params {
|
||||
Params::None => {
|
||||
let client = take_weak!(self.client);
|
||||
// check if we're still syncing and return empty strings int that case
|
||||
// check if we're still syncing and return empty strings in that case
|
||||
{
|
||||
let sync = take_weak!(self.sync);
|
||||
if sync.status().state != SyncState::Idle && client.queue_info().is_empty() {
|
||||
//TODO: check if initial sync is complete here
|
||||
//let sync = take_weak!(self.sync);
|
||||
if /*sync.status().state != SyncState::Idle ||*/ client.queue_info().total_queue_size() > MAX_QUEUE_SIZE_TO_MINE_ON {
|
||||
trace!(target: "miner", "Syncing. Cannot give any work.");
|
||||
return to_value(&(String::new(), String::new(), String::new()));
|
||||
}
|
||||
}
|
||||
|
@ -476,7 +476,8 @@ fn rpc_eth_compile_serpent() {
|
||||
|
||||
#[test]
|
||||
fn returns_no_work_if_cant_mine() {
|
||||
let eth_tester = EthTester::default();
|
||||
let mut eth_tester = EthTester::default();
|
||||
eth_tester.client.set_queue_size(10);
|
||||
|
||||
let request = r#"{"jsonrpc": "2.0", "method": "eth_getWork", "params": [], "id": 1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":["","",""],"id":1}"#;
|
||||
|
14
rustfmt.toml
Normal file
14
rustfmt.toml
Normal file
@ -0,0 +1,14 @@
|
||||
verbose=false
|
||||
max_width=150
|
||||
ideal_width=120
|
||||
tabs_spaces=4
|
||||
fn_call_width=100
|
||||
single_line_if_else=true
|
||||
where_indent="Visual"
|
||||
where_trailing_comma=true
|
||||
chain_base_indent="Inherit"
|
||||
chain_indent="Tabbed"
|
||||
reorder_imports=true
|
||||
format_strings=false
|
||||
hard_tabs=true
|
||||
wrap_match_arms=false
|
@ -1 +0,0 @@
|
||||
hard_tabs = true
|
@ -208,7 +208,7 @@ pub mod ec {
|
||||
match context.verify(&try!(Message::from_slice(&message)), &sig, &publ) {
|
||||
Ok(_) => Ok(true),
|
||||
Err(Error::IncorrectSignature) => Ok(false),
|
||||
Err(x) => Err(<CryptoError as From<Error>>::from(x))
|
||||
Err(x) => Err(CryptoError::from(x))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,6 +124,7 @@ pub mod hash;
|
||||
pub mod bytes;
|
||||
pub mod rlp;
|
||||
pub mod misc;
|
||||
pub mod using_queue;
|
||||
mod json_aid;
|
||||
pub mod vector;
|
||||
pub mod sha3;
|
||||
@ -149,6 +150,7 @@ pub mod table;
|
||||
|
||||
pub use common::*;
|
||||
pub use misc::*;
|
||||
pub use using_queue::*;
|
||||
pub use json_aid::*;
|
||||
pub use rlp::*;
|
||||
pub use hashdb::*;
|
||||
|
@ -153,7 +153,7 @@ impl<'a, 'view> Iterator for RlpIterator<'a, 'view> {
|
||||
|
||||
fn next(&mut self) -> Option<Rlp<'a>> {
|
||||
let index = self.index;
|
||||
let result = self.rlp.rlp.at(index).ok().map(| iter | { From::from(iter) });
|
||||
let result = self.rlp.rlp.at(index).ok().map(From::from);
|
||||
self.index += 1;
|
||||
result
|
||||
}
|
||||
|
207
util/src/using_queue.rs
Normal file
207
util/src/using_queue.rs
Normal file
@ -0,0 +1,207 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
//! Queue-like datastructure including notion of usage.
|
||||
|
||||
/// Special queue-like datastructure that includes the notion of
|
||||
/// usage to avoid items that were queued but never used from making it into
|
||||
/// the queue.
|
||||
pub struct UsingQueue<T> where T: Clone {
|
||||
/// Not yet being sealed by a miner, but if one asks for work, we'd prefer they do this.
|
||||
pending: Option<T>,
|
||||
/// Currently being sealed by miners.
|
||||
in_use: Vec<T>,
|
||||
/// The maximum allowable number of items in_use.
|
||||
max_size: usize,
|
||||
}
|
||||
|
||||
impl<T> UsingQueue<T> where T: Clone {
|
||||
/// Create a new struct with a maximum size of `max_size`.
|
||||
pub fn new(max_size: usize) -> UsingQueue<T> {
|
||||
UsingQueue {
|
||||
pending: None,
|
||||
in_use: vec![],
|
||||
max_size: max_size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a reference to the item at the top of the queue (or `None` if the queue is empty);
|
||||
/// it doesn't constitute noting that the item is used.
|
||||
pub fn peek_last_ref(&self) -> Option<&T> {
|
||||
self.pending.as_ref().or(self.in_use.last())
|
||||
}
|
||||
|
||||
/// Return a reference to the item at the top of the queue (or `None` if the queue is empty);
|
||||
/// this constitutes using the item and will remain in the queue for at least another
|
||||
/// `max_size` invocations of `push()`.
|
||||
pub fn use_last_ref(&mut self) -> Option<&T> {
|
||||
if let Some(x) = self.pending.take() {
|
||||
self.in_use.push(x);
|
||||
if self.in_use.len() > self.max_size {
|
||||
self.in_use.remove(0);
|
||||
}
|
||||
}
|
||||
self.in_use.last()
|
||||
}
|
||||
|
||||
/// Place an item on the end of the queue. The previously `push()`ed item will be removed
|
||||
/// if `use_last_ref()` since it was `push()`ed.
|
||||
pub fn push(&mut self, b: T) {
|
||||
self.pending = Some(b);
|
||||
}
|
||||
|
||||
/// Clears everything; the queue is entirely reset.
|
||||
pub fn reset(&mut self) {
|
||||
self.pending = None;
|
||||
self.in_use.clear();
|
||||
}
|
||||
|
||||
/// Returns `Some` reference to first block that `f` returns `true` with it as a parameter
|
||||
/// or `None` if no such block exists in the queue.
|
||||
pub fn find_if<P>(&self, predicate: P) -> Option<&T> where P: Fn(&T) -> bool {
|
||||
if self.pending.as_ref().map(|r| predicate(r)).unwrap_or(false) {
|
||||
self.pending.as_ref()
|
||||
} else {
|
||||
self.in_use.iter().find(|r| predicate(r))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the most recently pushed block if `f` returns `true` with a reference to it as
|
||||
/// a parameter, otherwise `None`.
|
||||
/// Will not destroy a block if a reference to it has previously been returned by `use_last_ref`,
|
||||
/// but rather clone it.
|
||||
pub fn pop_if<P>(&mut self, predicate: P) -> Option<T> where P: Fn(&T) -> bool {
|
||||
// a bit clumsy - TODO: think about a nicer way of expressing this.
|
||||
if let Some(x) = self.pending.take() {
|
||||
if predicate(&x) {
|
||||
Some(x)
|
||||
} else {
|
||||
self.pending = Some(x);
|
||||
None
|
||||
}
|
||||
} else {
|
||||
self.in_use.last().into_iter().filter(|x| predicate(x)).next().cloned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_find_when_pushed() {
|
||||
let mut q = UsingQueue::new(2);
|
||||
q.push(1);
|
||||
assert!(q.find_if(|i| i == &1).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_find_when_pushed_and_used() {
|
||||
let mut q = UsingQueue::new(2);
|
||||
q.push(1);
|
||||
q.use_last_ref();
|
||||
assert!(q.find_if(|i| i == &1).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_find_when_others_used() {
|
||||
let mut q = UsingQueue::new(2);
|
||||
q.push(1);
|
||||
q.use_last_ref();
|
||||
q.push(2);
|
||||
q.use_last_ref();
|
||||
assert!(q.find_if(|i| i == &1).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_find_when_too_many_used() {
|
||||
let mut q = UsingQueue::new(1);
|
||||
q.push(1);
|
||||
q.use_last_ref();
|
||||
q.push(2);
|
||||
q.use_last_ref();
|
||||
assert!(q.find_if(|i| i == &1).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_find_when_not_used_and_then_pushed() {
|
||||
let mut q = UsingQueue::new(3);
|
||||
q.push(1);
|
||||
q.push(2);
|
||||
q.use_last_ref();
|
||||
assert!(q.find_if(|i| i == &1).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_peek_correctly_after_push() {
|
||||
let mut q = UsingQueue::new(3);
|
||||
q.push(1);
|
||||
assert_eq!(q.peek_last_ref(), Some(&1));
|
||||
q.push(2);
|
||||
assert_eq!(q.peek_last_ref(), Some(&2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_inspect_correctly() {
|
||||
let mut q = UsingQueue::new(3);
|
||||
q.push(1);
|
||||
assert_eq!(q.use_last_ref(), Some(&1));
|
||||
assert_eq!(q.peek_last_ref(), Some(&1));
|
||||
q.push(2);
|
||||
assert_eq!(q.use_last_ref(), Some(&2));
|
||||
assert_eq!(q.peek_last_ref(), Some(&2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_find_when_not_used_peeked_and_then_pushed() {
|
||||
let mut q = UsingQueue::new(3);
|
||||
q.push(1);
|
||||
q.peek_last_ref();
|
||||
q.push(2);
|
||||
q.use_last_ref();
|
||||
assert!(q.find_if(|i| i == &1).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_pop_used() {
|
||||
let mut q = UsingQueue::new(3);
|
||||
q.push(1);
|
||||
q.use_last_ref();
|
||||
let popped = q.pop_if(|i| i == &1);
|
||||
assert_eq!(popped, Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_pop_unused() {
|
||||
let mut q = UsingQueue::new(3);
|
||||
q.push(1);
|
||||
assert_eq!(q.pop_if(|i| i == &1), Some(1));
|
||||
assert_eq!(q.pop_if(|i| i == &1), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_pop_unused_before_used() {
|
||||
let mut q = UsingQueue::new(3);
|
||||
q.push(1);
|
||||
q.push(2);
|
||||
let popped = q.pop_if(|i| i == &1);
|
||||
assert_eq!(popped, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_remove_used_popped() {
|
||||
let mut q = UsingQueue::new(3);
|
||||
q.push(1);
|
||||
q.use_last_ref();
|
||||
assert_eq!(q.pop_if(|i| i == &1), Some(1));
|
||||
assert_eq!(q.pop_if(|i| i == &1), Some(1));
|
||||
}
|
Loading…
Reference in New Issue
Block a user