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

chore: tests for Lazy and moving out of unstable #1268

Merged
merged 19 commits into from
Dec 5, 2024
4 changes: 0 additions & 4 deletions near-sdk/src/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,10 @@
//! place of a type [`Option<T>`](Option). Will only be loaded when interacted with and will
//! persist on [`Drop`].

#[cfg(feature = "unstable")]
mod lazy;
#[cfg(feature = "unstable")]
pub use lazy::Lazy;

#[cfg(feature = "unstable")]
mod lazy_option;
#[cfg(feature = "unstable")]
pub use lazy_option::LazyOption;

pub mod vec;
Expand Down
100 changes: 88 additions & 12 deletions near-sdk/tests/store_performance_tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// As wasm VM performance is tested, there is no need to test this on other types of OS.
// This test runs only on Linux, as it's much slower on OS X due to an interpreted VM.
#![cfg(target_os = "linux")]
// #![cfg(target_os = "linux")]

use near_account_id::AccountId;
use near_gas::NearGas;
Expand All @@ -9,6 +9,7 @@ use near_workspaces::types::{KeyType, SecretKey};
use near_workspaces::{Account, Worker};
use rand::Rng;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::sync::Arc;
use strum_macros::Display;

Expand All @@ -27,6 +28,11 @@ pub enum Collection {
Vector,
}

pub enum Contract {
StoreContract,
LazyContract,
}

fn random_account_id(collection: Collection, seed: &str) -> AccountId {
let mut rng = rand::thread_rng();
let random_num = rng.gen_range(10000000000000usize..99999999999999);
Expand All @@ -53,16 +59,20 @@ async fn dev_generate(
Ok((account.into_result()?, collection))
}

async fn setup_worker() -> anyhow::Result<(Arc<Worker<Sandbox>>, AccountId)> {
async fn setup_worker(contract: Contract) -> anyhow::Result<(Arc<Worker<Sandbox>>, AccountId)> {
let contract_path = match contract {
Contract::StoreContract => "./tests/test-contracts/store",
Contract::LazyContract => "./tests/test-contracts/lazy",
};
let worker = Arc::new(near_workspaces::sandbox().await?);
let wasm = near_workspaces::compile_project("./tests/test-contracts/store").await?;
let wasm = near_workspaces::compile_project(contract_path).await?;
let contract = worker.dev_deploy(&wasm).await?;
let res = contract.call("new").max_gas().transact().await?;
assert!(res.is_success());
Ok((worker, contract.id().clone()))
}

fn perform_asserts(total_gas: u64, col: &Collection) {
fn perform_asserts(total_gas: u64, col: impl Display) {
// Constraints a bit relaxed to account for binary differences due to on-demand compilation.
assert!(
total_gas < NearGas::from_tgas(110).as_gas(),
Expand All @@ -80,7 +90,7 @@ fn perform_asserts(total_gas: u64, col: &Collection) {

#[allow(unused)]
async fn setup_several(num: usize) -> anyhow::Result<(Vec<Account>, AccountId)> {
let (worker, contract_id) = setup_worker().await?;
let (worker, contract_id) = setup_worker(Contract::StoreContract).await?;
let mut accounts = Vec::new();

for acc_seed in 0..num {
Expand All @@ -92,8 +102,8 @@ async fn setup_several(num: usize) -> anyhow::Result<(Vec<Account>, AccountId)>
Ok((accounts, contract_id))
}

async fn setup() -> anyhow::Result<(Account, AccountId)> {
let (worker, contract_id) = setup_worker().await?;
async fn setup(contract: Contract) -> anyhow::Result<(Account, AccountId)> {
let (worker, contract_id) = setup_worker(contract).await?;

let (account, _) =
dev_generate(worker.clone(), Collection::IterableSet, "seed".to_string()).await?;
Expand All @@ -114,7 +124,7 @@ async fn insert_and_remove() -> anyhow::Result<()> {
Collection::Vector,
];

let (account, contract_id) = setup().await?;
let (account, contract_id) = setup(Contract::StoreContract).await?;
// insert test, max_iterations here is the number of elements to insert. It's used to measure
// relative performance.
for (col, max_iterations) in collection_types.map(|col| match col {
Expand Down Expand Up @@ -181,7 +191,7 @@ async fn iter() -> anyhow::Result<()> {
];

let element_number = 100;
let (account, contract_id) = setup().await?;
let (account, contract_id) = setup(Contract::StoreContract).await?;

// pre-populate
for col in collection_types {
Expand Down Expand Up @@ -234,7 +244,7 @@ async fn random_access() -> anyhow::Result<()> {
Collection::Vector,
];
let element_number = 100;
let (account, contract_id) = setup().await?;
let (account, contract_id) = setup(Contract::StoreContract).await?;

// pre-populate
for col in collection_types {
Expand Down Expand Up @@ -297,7 +307,7 @@ async fn contains() -> anyhow::Result<()> {
];
// Each collection gets the same number of elements.
let element_number = 100;
let (account, contract_id) = setup().await?;
let (account, contract_id) = setup(Contract::StoreContract).await?;

// prepopulate
for col in collection_types {
Expand Down Expand Up @@ -344,7 +354,7 @@ async fn contains() -> anyhow::Result<()> {
async fn iterable_vs_unordered() -> anyhow::Result<()> {
let element_number = 300;
let deleted_element_number = 299;
let (account, contract_id) = setup().await?;
let (account, contract_id) = setup(Contract::StoreContract).await?;

// We only care about Unordered* and Iterable* collections.
let collection_types = &[
Expand Down Expand Up @@ -422,3 +432,69 @@ async fn iterable_vs_unordered() -> anyhow::Result<()> {

Ok(())
}

#[tokio::test]
async fn test_lazy() -> anyhow::Result<()> {
let (account, contract_id) = setup(Contract::LazyContract).await?;

let res = account
.call(&contract_id, "insert_delete")
.args_json((700,))
.max_gas()
.transact()
.await?
.unwrap();

perform_asserts(res.total_gas_burnt.as_gas(), "lazy:insert_delete");

let res = account
.call(&contract_id, "insert_delete_flush_once")
.args_json((1700,))
.max_gas()
.transact()
.await?
.unwrap();

perform_asserts(res.total_gas_burnt.as_gas(), "lazy:insert_delete_flush_once");

let res = account
.call(&contract_id, "flush")
.args_json((2450000,))
.max_gas()
.transact()
.await?
.unwrap();

perform_asserts(res.total_gas_burnt.as_gas(), "lazy:flush");

let res = account
.call(&contract_id, "get")
.args_json((3500000,))
.max_gas()
.transact()
.await?
.unwrap();

perform_asserts(res.total_gas_burnt.as_gas(), "lazy:get");

let res = account
.call(&contract_id, "insert_flush")
.args_json((1200,))
.max_gas()
.transact()
.await?
.unwrap();

perform_asserts(res.total_gas_burnt.as_gas(), "lazy:insert_flush");

let res = account
.call(&contract_id, "insert_take_flush")
.args_json((700,))
.max_gas()
.transact()
.await?
.unwrap();

perform_asserts(res.total_gas_burnt.as_gas(), "lazy:insert_take_flush");
Ok(())
akorchyn marked this conversation as resolved.
Show resolved Hide resolved
}
12 changes: 12 additions & 0 deletions near-sdk/tests/test-contracts/lazy/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "lazy"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
near-sdk = { path = "../../../../near-sdk", features = ["default", "unstable"] }

[workspace]
96 changes: 96 additions & 0 deletions near-sdk/tests/test-contracts/lazy/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use near_sdk::borsh::{BorshDeserialize, BorshSerialize};
use near_sdk::{near, store::LazyOption, PanicOnDefault};

#[derive(BorshSerialize, BorshDeserialize, Ord, PartialOrd, Eq, PartialEq, Clone)]
#[borsh(crate = "near_sdk::borsh")]
pub struct Insertable {
pub index: u32,
pub data: String,
pub is_valid: bool,
}

#[near(contract_state)]
#[derive(PanicOnDefault)]
pub struct LazyContract {
pub lazy_opt: LazyOption<Insertable>,
}

#[near]
impl LazyContract {
#[init]
pub fn new() -> Self {
let lazy_opt = LazyOption::new(b"a", None);
Self { lazy_opt }
}

fn insertable(&self) -> Insertable {
Insertable { index: 0, data: "scatter cinnamon wheel useless please rough situate iron eager noise try evolve runway neglect onion".to_string(), is_valid: true }
}

/// This should only write to the underlying storage once.
#[payable]
pub fn flush(&mut self, iterations: usize) {
let insertable = self.insertable();
self.lazy_opt.set(Some(insertable));

for _ in 0..=iterations {
self.lazy_opt.flush();
}
}

#[payable]
pub fn get(&mut self, iterations: u32) {
let insertable = self.insertable();
self.lazy_opt.set(Some(insertable));
for _ in 0..=iterations {
self.lazy_opt.get();
}
}

/// This should write on each iteration.
#[payable]
pub fn insert_flush(&mut self, iterations: u32) {
let mut insertable = self.insertable();
for idx in 0..=iterations {
insertable.index = idx as u32;
self.lazy_opt.set(Some(insertable.clone()));
self.lazy_opt.flush();
}
}

/// This should write twice on each iteration.
#[payable]
pub fn insert_take_flush(&mut self, iterations: u32) {
Copy link
Collaborator

@akorchyn akorchyn Dec 3, 2024

Choose a reason for hiding this comment

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

I would name it insert_take to be consistent with insert_delete.

As I got confused initially that it is same as insert_delete_flush_once

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

will do

let mut insertable = self.insertable();
for idx in 0..=iterations {
insertable.index = idx as u32;
self.lazy_opt.set(Some(insertable.clone()));
self.lazy_opt.flush();
self.lazy_opt.take();
self.lazy_opt.flush();
}
}

/// This should write and delete on each iteration.
#[payable]
akorchyn marked this conversation as resolved.
Show resolved Hide resolved
pub fn insert_delete(&mut self, iterations: u32) {
let insertable = self.insertable();
for _ in 0..=iterations {
self.lazy_opt.set(Some(insertable.clone()));
self.lazy_opt.flush();
self.lazy_opt.set(None);
self.lazy_opt.flush();
}
}

/// This should write once on each iteration.
#[payable]
pub fn insert_delete_flush_once(&mut self, iterations: u32) {
let insertable = self.insertable();
for _ in 0..=iterations {
self.lazy_opt.set(Some(insertable.clone()));
self.lazy_opt.set(None);
self.lazy_opt.flush();
}
}
}
Loading