Feature Parity Testing (Özellik Parite Testi)

Programınızı test ederken, çeşitli kümelerde aynı şekilde çalışacağının güvencesi, hem kalite hem de beklenen sonuçların üretilmesi için esastır.

Facts (Özet Bilgiler)

:::Bilgi Tablosu

Features, Solana doğrulayıcılarına sunulan ve kullanılması için etkinleştirme gerektiren yeteneklerdir. Features bir kümede (ör. testnet) etkinleştirilebilir, ancak başka bir kümede (ör. ana ağ-beta) etkinleştirilemez. Yine de; varsayılan solana-test-validator'ı yerel olarak çalıştırırken, Solana sürümünüzdeki mevcut tüm özellikler otomatik olarak etkinleştirilir. Sonuç olarak, yerel olarak test ederken, farklı bir kümede dağıtırken ve çalıştırırken testinizin yetenekleri ve sonuçları aynı olmayabilir! :::

Scenario (Senaryo)

Üç (3) talimat içeren bir işleminiz olduğunu ve her talimatın yaklaşık 100_000 İşlem Birimi (Compute Unit - CU) tükettiğini varsayalım. Bir Solana 1.8.x sürümünde çalışırken, talimat CU tüketiminizi aşağıdakine benzer şekilde gözlemlersiniz:

InstructionStarting CUExecutionRemaining CU
1200_000-100_000100_000
2200_000-100_000100_000
3200_000-100_000100_000

Solana 1.9.2'de, bir işlemin varsayılan olarak 200_000 CU bütçesine sahip olduğu ve kapsüllenmiş talimatların bu işlem bütçesinden çekildiği 'transaction işlem sınırı' adı verilen bir özellik vardır. Yukarıda belirtildiği gibi aynı işlemi çalıştırmak çok farklı davranışlara sahip olacaktır:

InstructionStarting CUExecutionRemaining CU
1200_000-100_000100_000
2100_000-100_0000
30HATA!!!HATA!!!

Evet! Bundan habersiz olsaydınız, talimat davranışınızda buna neden olacak bir değişiklik olmadığı için muhtemelen hayal kırıklığına uğrarsınız. Devnet'te iyi çalıştı, ancak yerel olarak başarısız mı oldu?!?

Örneğin 300_000 CU gibi genel İşlem bütçesini artırma ve akıl sağlığınızı koruma olanağı vardır, ancak bu, Feature Parity (Özellik Eşliği) ile testin neden herhangi bir karışıklığı önlemek için proaktif bir yol sağladığını gösterir.

Feature Status (Özellik Durumu)

Solana feature status komutuyla belirli bir küme için hangi özelliklerin etkinleştirildiğini kontrol etmek oldukça kolaydır.

solana feature status -ud   // Displays by feature status for devnet
solana feature status -ut   // Displays for testnet
solana feature status -um   // Displays for mainnet-beta
solana feature status -ul   // Displays for local, requires running solana-test-validator

Alternatif olarak, kümeler genelinde tüm özellik durumunu gözlemlemek için solana-test-validator'ın çalışmasını gerektirmeyen ve aşağıdaki ekranda gösterilen scfsd gibi bir araç kullanabilirsiniz:

Feature Status Heatmap

Parity Testing (Parite Testi)

Yukarıda belirtildiği gibi, solana-test-validator, tüm özellikleri otomatik olarak etkinleştirir. "Devnet, testnet ve hatta mainnet-beta ile paritesi olan bir ortamda yerel olarak nasıl test edebilirim?" sorusuna cevap verecek olursak:

Çözüm: Özelliklerin devre dışı bırakılmasına izin vermek için Solana 1.9.6'ya PR'ler eklendi:

solana-test-validator --deactivate-feature <FEATURE_PUBKEY> ...

Simple Demonstration (Basit Gösterim)

Aldığı verileri giriş noktasında kaydeden basit bir programınız olduğunu varsayalım. Ve programınız için iki (2) talimat ekleyen bir işlemi test ediyorsunuz.

All features activated (Tüm özellikler aktifleştirildi)

  1. Test doğrulayıcıyı bir terminalde başlatırsınız:
solana config set -ul
solana-test-validator -l ./ledger --bpf-program ADDRESS target/deploy/PROGNAME.so --reset`
  1. Başka bir terminalde günlük aktarıcıyı başlatırsınız:
solana logs
  1. Daha sonra işleminizi yürütürsünüz. Günlük terminalinde benzer bir şey görürsünüz (netlik için düzenlenmiştir):
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc invoke [1]
Program log: process_instruction: PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc: 0 accounts, data=[0]
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc consumed 12843 of 200000 compute units
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc success
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc invoke [1]
Program log: process_instruction: PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc: 0 accounts, data=[1]
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc consumed 12843 of 187157 compute units
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc success[

'transaction işlem sınırı' özelliğimiz varsayılan olarak otomatik olarak etkinleştirildiğinden, her talimatın CU'yu başlangıç İşlem bütçesi olan 200_000 CU'dan aşağı çektiğini gözlemleriz.

Selective features deactivated (Seçici özellikler devre dışı bırakıldı)

Bu çalıştırma için, CU bütçe davranışının devnet'te yürütülenle aynı olması için çalıştırmak istiyoruz. Feature Status’ta açıklanan araç(lar)ı kullanarak, işlem genelinde işlem üst sınırı public key’i izole eder ve test doğrulayıcı başlangıcında --deactivate-feature kullanırız.

solana-test-validator -l ./ledger --deactivate-feature 5ekBxc8itEnPv4NzGJtr8BVVQLNMQuLMNQQj7pHoLNZ9 --bpf-program target/deploy/PROGNAME.so --reset`

Artık günlüklerimizde, talimatlarımızın şu anda tüm yukarı akış kümelerinde durum olan kendi 200_000 CU bütçesine (açıklık için düzenlenmiş) sahip olduğunu görüyoruz:

Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc invoke [1]
Program log: process_instruction: PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc: 0 accounts, data=[0]
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc consumed 12843 of 200000 compute units
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc success
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc invoke [1]
Program log: process_instruction: PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc: 0 accounts, data=[0]
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc consumed 12843 of 200000 compute units
Program PWDnx8LkjJUn9bAVzG6Fp6BuvB41x7DkBZdo9YLMGcc success

Full Parity Testing (Tam Parite Testi) Henüz etkinleştirilmemiş her bir özelliği tanımlayarak ve solana-test-validator'ı çağırırken her biri için bir --deactivate-feature <FEATURE_PUBKEY> ekleyerek belirli bir küme ile tam eşlilik içinde olabilirsiniz:

solana-test-validator --deactivate-feature PUBKEY_1 --deactivate-feature PUBKEY_2 ...

Alternatif olarak, scfsd, bir kümenin doğrudan solana-test-validator başlangıcına beslenmesi için tüm devre dışı bırakılmış özellik setinin çıktısını almak için bir komut anahtarı sağlar:

solana-test-validator -l ./.ledger $(scfsd -c devnet -k -t)

Doğrulayıcı çalışırken başka bir terminal açarsanız ve solana feature status’ın devnet'te devre dışı bulunan özelliklerin devre dışı olduğunu göreceksiniz.

Full Parity Testing Programmatically (Programlı Olarak Tam Parite Testi)

Test doğrulayıcının kendi test kodu içinde çalıştırılmasını kontrol edenler için, TestValidatorGenesis kullanılarak test doğrulayıcı etkinleştirilen/devre dışı bırakılan özelliklerin değiştirilmesi mümkündür. Solana 1.9.6 ile bunu desteklemek için doğrulayıcı oluşturucuya bir işlev eklendi.

Program klasörünüzün kökünde, tests adlı yeni bir klasör oluşturun ve bir parity_test.rs dosyası ekleyin. İşte her test tarafından kullanılan bazı genel fonksiyonlar:

/// Setup the test validator passing features
/// you want to deactivate before running transactions
pub fn setup_validator(
    invalidate_features: Vec<Pubkey>,
) -> Result<(TestValidator, Keypair), Box<dyn error::Error>> {
    // Extend environment variable to include our program location
    std::env::set_var("BPF_OUT_DIR", PROG_PATH);
    // Instantiate the test validator
    let mut test_validator = TestValidatorGenesis::default();
    // Once instantiated, TestValidatorGenesis configuration functions follow
    // a builder pattern enabling chaining of settings function calls
    let (test_validator, kp) = test_validator
        // Set the ledger path and name
        // maps to `solana-test-validator --ledger <DIR>`
        .ledger_path(LEDGER_PATH)
        // Load our program. Ignored if reusing ledger
        // maps to `solana-test-validator --bpf-program <ADDRESS_OR_PATH BPF_PROGRAM.SO>`
        .add_program(PROG_NAME, PROG_KEY)
        // Identify features to deactivate. Ignored if reusing ledger
        // maps to `solana-test-validator --deactivate-feature <FEATURE_PUBKEY>`
        .deactivate_features(&invalidate_features)
        // Start the test validator
        .start();
    Ok((test_validator, kp))
}

/// Convenience function to remove existing ledger before TestValidatorGenesis setup
/// maps to `solana-test-validator ... --reset`
pub fn clean_ledger_setup_validator(
    invalidate_features: Vec<Pubkey>,
) -> Result<(TestValidator, Keypair), Box<dyn error::Error>> {
    if PathBuf::from_str(LEDGER_PATH).unwrap().exists() {
        std::fs::remove_dir_all(LEDGER_PATH).unwrap();
    }
    setup_validator(invalidate_features)
}

/// Submits a transaction with programs instruction
/// Boiler plate
fn submit_transaction(
    rpc_client: &RpcClient,
    wallet_signer: &dyn Signer,
    instructions: Vec<Instruction>,
) -> Result<Signature, Box<dyn std::error::Error>> {
    let mut transaction =
        Transaction::new_unsigned(Message::new(&instructions, Some(&wallet_signer.pubkey())));
    let recent_blockhash = rpc_client
        .get_latest_blockhash()
        .map_err(|err| format!("error: unable to get recent blockhash: {}", err))?;
    transaction
        .try_sign(&vec![wallet_signer], recent_blockhash)
        .map_err(|err| format!("error: failed to sign transaction: {}", err))?;
    let signature = rpc_client
        .send_and_confirm_transaction(&transaction)
        .map_err(|err| format!("error: send transaction: {}", err))?;
    Ok(signature)
}

Şimdi, mod test {...} gövdesine test işlevleri ekleyebiliriz. Bu sayede, varsayılan doğrulayıcı kurulumunu (tüm özellikler etkin) gösterebilir ve ardından transaction wide compute cap’i devre dışı bırakibiliriz (solana-test-validator çalıştıran önceki örneklerde olduğu gibi).

#[test]
fn test_deactivate_tx_cu_pass() {
    // Run with all features activated except 'transaction wide compute cap'
    let inv_feat = vec![TXWIDE_LIMITS];
    // Start validator with clean (new) ledger
    let (test_validator, main_payer) = clean_ledger_setup_validator(inv_feat).unwrap();
    // Get the RpcClient
    let connection = test_validator.get_rpc_client();
    // Capture our programs log statements
    solana_logger::setup_with_default("solana_runtime::message=debug");

    // This example doesn't require sending any accounts to program
    let accounts = &[];
    // Build instruction array and submit transaction
    let txn = submit_transaction(
        &connection,
        &main_payer,
        [
            // This instruction adds CU to transaction budget (1.9.2) but does nothing
            // when we deactivate the 'transaction wide compute cap' feature
            ComputeBudgetInstruction::request_units(400_000u32),
            // Add two (2) instructions to transaction
            // Replace with instructions that make sense for your program
            // You will see that each instruction has the 1.8.x 200_000 CU per budget
            Instruction::new_with_borsh(PROG_KEY, &0u8, accounts.to_vec()),
            Instruction::new_with_borsh(PROG_KEY, &1u8, accounts.to_vec()),
        ]
        .to_vec(),
    );
    assert!(txn.is_ok());
}

Alternatif olarak, scfs engine gadget'ı, bir küme için tam bir devre dışı bırakılmış özellikler vektörü üretebilir. Aşağıdaki, devnet için tüm devre dışı bırakılmış özelliklerin bir listesini almak için bu motorun kullanıldığını gösterir.

#[test]
fn test_devnet_parity_pass() {
    // Use gadget-scfs to get all deactivated features from devnet
    // must have `gadgets-scfs = "0.2.0" in Cargo.toml to use
    // Here we setup for a run that samples features only
    // from devnet
    let mut my_matrix = ScfsMatrix::new(Some(ScfsCriteria {
        clusters: Some(vec![SCFS_DEVNET.to_string()]),
        ..Default::default()
    }))
    .unwrap();
    // Run the sampler matrix
    assert!(my_matrix.run().is_ok());
    // Get all deactivated features
    let deactivated = my_matrix
        .get_features(Some(&ScfsMatrix::any_inactive))
        .unwrap();
    // Confirm we have them
    assert_ne!(deactivated.len(), 0);
    // Setup test validator and logging while deactivating all
    // features that are deactivated in devnet
    let (test_validator, main_payer) = clean_ledger_setup_validator(deactivated).unwrap();
    let connection = test_validator.get_rpc_client();
    solana_logger::setup_with_default("solana_runtime::message=debug");

    let accounts = &[];
    let txn = submit_transaction(
        &connection,
        &main_payer,
        [
            // Add two (2) instructions to transaction
            // Replace with instructions that make sense for your program
            Instruction::new_with_borsh(PROG_KEY, &0u8, accounts.to_vec()),
            Instruction::new_with_borsh(PROG_KEY, &1u8, accounts.to_vec()),
        ]
        .to_vec(),
    );
    assert!(txn.is_ok());
}

Mutlu Testler!

Resources

scfsdopen in new window

gadget-scfsopen in new window

Last Updated:
Contributors: rbeyzas