[][src]Struct exonum_testkit::migrations::MigrationTest

pub struct MigrationTest<S> { /* fields omitted */ }

Helper for migration testing.

The helper implements the following workflow:

  1. Prepare test data to be migrated using setup.
  2. Execute one or more migration scripts using execute_script or migrate.
  3. Check that the migrated data is valid using the end_snapshot of the database and, possible, the snapshot before migration.

Examples

Testing a single migration script:

use exonum::merkledb::access::AccessExt;
use exonum_testkit::migrations::{MigrationTest, ScriptExt};

fn script_under_test(ctx: &mut MigrationContext) -> Result<(), MigrationError> {
    let old_data = ctx.helper.old_data();
    let old_value = old_data.get_entry::<_, u32>("entry").get().unwrap_or(0);
    let new_data = ctx.helper.new_data();
    new_data.get_proof_entry("entry").set(old_value + 1);
    Ok(())
}

/// Service under test.
#[derive(Debug, ServiceDispatcher, ServiceFactory)]
#[service_factory(artifact_name = "test-service")]
pub struct ServiceUnderTest;

impl Service for ServiceUnderTest {}

let mut test = MigrationTest::new(ServiceUnderTest, Version::new(0, 1, 0));
let snapshot = test
    .setup(|access| {
        // Setup data for the test, for example, create the old service schema
        // and add test data into it.
        access.get_entry("entry").set(1_u32);
    })
    .execute_script(script_under_test.with_end_version("0.2.0"))
    .end_snapshot();
// Check the data in the snapshot after the script is executed.
let value = snapshot.get_proof_entry::<_, u32>("entry").get();
assert_eq!(value, Some(2));

Testing fault tolerance of a script:

use exonum::merkledb::access::AccessExt;
use exonum_testkit::migrations::{AbortPolicy, MigrationTest, ScriptExt};

fn script_with_merges(ctx: &mut MigrationContext) -> Result<(), MigrationError> {
    let new_data = ctx.helper.new_data();
    let mut counter = new_data.get_entry::<_, u32>("counter").get().unwrap_or(0);
    while counter < 5 {
        counter += 1;
        ctx.helper.new_data().get_entry("counter").set(counter);
        ctx.helper.merge()?;
    }
    Ok(())
}

let mut test = MigrationTest::new(ServiceUnderTest, Version::new(0, 1, 0));
let end_snapshot = test
    .execute_until_flush(
        || script_with_merges.with_end_version("0.2.0"),
        AbortPolicy::abort_repeatedly(),
    )
    .end_snapshot();
// The counter value should be set to 5.
let counter = end_snapshot.get_entry::<_, u32>("counter").get();
assert_eq!(counter, Some(5));

Testing that a script makes progress (this one doesn't):

fn infinite_script(ctx: &mut MigrationContext) -> Result<(), MigrationError> {
    for counter in 0_u32..5 {
        ctx.helper.new_data().get_entry("counter").set(counter);
        ctx.helper.merge()?;
    }
    // To get here, the script requires 5 successive database merges to succeed.
    Ok(())
}

let mut test = MigrationTest::new(ServiceUnderTest, Version::new(0, 1, 0));
test.execute_until_flush(
    || infinite_script.with_end_version("0.2.0"),
    // This policy does not generate 5 successful merges in a row, so the script
    // doesn't make any progress. Due to `limit_merges`, the test will panic
    // rather than hang up.
    AbortPolicy::abort_repeatedly().limit_merges(100),
);

Methods

impl<S> MigrationTest<S> where
    S: ServiceFactory
[src]

pub fn new(service_factory: S, start_version: Version) -> Self[src]

Initializes a test with the given start version of the artifact.

pub fn setup<F>(&mut self, setup: F) -> &mut Self where
    F: FnOnce(Prefixed<&Fork>), 
[src]

Sets up initial data for the service before the migration.

pub fn start_snapshot(&self) -> Prefixed<&dyn Snapshot>[src]

Gets the snapshot before the migration scripts are run.

pub fn migration_data(&self) -> Migration<&dyn Snapshot>[src]

Gets the migrated data. This method is useful to inspect migration state after script abortion. Once the migration is flushed, the migrated data is erased.

pub fn end_snapshot(&self) -> Prefixed<&dyn Snapshot>[src]

Gets the snapshot at the end of the migration. If the latest migration script execution was aborted, this method will provide access to old data since the migration is not flushed in this case.

pub fn execute_script(&mut self, script: MigrationScript) -> &mut Self[src]

Executes a single migration script.

pub fn execute_script_with_aborts(
    &mut self,
    script: MigrationScript,
    abort_handle: impl AbortMigration + 'static
) -> ScriptStatus
[src]

Executes a migration script with the specified abort policy. Each time the script merges changes to the database, the policy will be queried whether to proceed or emulate script abort.

Return Value

Returns status of the script indicating whether it completed successfully or was aborted.

pub fn execute_until_flush<F, T>(
    &mut self,
    script_fn: F,
    aborts: T
) -> &mut Self where
    F: FnMut() -> MigrationScript,
    T: AbortMigration + Send + Sync + 'static, 
[src]

Repeatedly executes a migration script with the provided abort policy until the script is completed (i.e., returns Ok(_)). The migration is flushed after this method returns.

The policy should be configured in such a way that it eventually progresses the script. A policy that aborts the script every time will lead to a hang-up if the script has at least one merge. AbortPolicy automatically satisfies this requirement; after each abort, it allows at least one merge.

impl<S> MigrationTest<S> where
    S: ServiceFactory + MigrateData
[src]

pub fn migrate(&mut self) -> &mut Self[src]

Performs the migration based on the MigrateData implementation.

Trait Implementations

impl<S: Debug> Debug for MigrationTest<S>[src]

Auto Trait Implementations

impl<S> !RefUnwindSafe for MigrationTest<S>

impl<S> Send for MigrationTest<S> where
    S: Send

impl<S> Sync for MigrationTest<S> where
    S: Sync

impl<S> Unpin for MigrationTest<S> where
    S: Unpin

impl<S> !UnwindSafe for MigrationTest<S>

Blanket Implementations

impl<T> Any for T where
    T: 'static + ?Sized
[src]

impl<T> Borrow<T> for T where
    T: ?Sized
[src]

impl<T> BorrowMut<T> for T where
    T: ?Sized
[src]

impl<T> From<T> for T[src]

impl<T, U> Into<U> for T where
    U: From<T>, 
[src]

impl<T, U> TryFrom<U> for T where
    U: Into<T>, 
[src]

type Error = Infallible

The type returned in the event of a conversion error.

impl<T, U> TryInto<U> for T where
    U: TryFrom<T>, 
[src]

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.

impl<V, T> VZip<V> for T where
    V: MultiLane<T>,