sui icon indicating copy to clipboard operation
sui copied to clipboard

Sui doc content issue or request https://docs.sui.io/build/move/build-test#testing-a-package

Open jymchng opened this issue 2 years ago • 3 comments

Hi, I'm new to move and sui-move.

test_scenario codes have been updated but not the corresponding docs on: https://docs.sui.io/build/move/build-test#testing-a-package.

Anyway, I edited the codes on the docs to:

#[test]
fun test_sword_transactions() {
    use sui::test_scenario;

    let admin = @0xABBA;
    let initial_owner = @0xCAFE;
    let final_owner = @0xFACE;

    // first transaction executed by admin
    let scenario = test_scenario::begin(admin);
    {
        // create the sword and transfer it to the initial owner
        sword_create(42, 7, initial_owner, test_scenario::ctx(&mut scenario));
        // anywhere for any expression where a &T value is used, a &mut T value can also be used
    // second transaction executed by the initial sword owner
    test_scenario::next_tx(&mut scenario, initial_owner);
    {
        // extract the sword owned by the initial owner
        let sword = test_scenario::take_from_sender<Sword>(&mut scenario);
        // transfer the sword to the final owner
        sword_transfer(sword, final_owner, test_scenario::ctx(&mut scenario));
    };
    // third transaction executed by the final sword owner
    test_scenario::next_tx(&mut scenario, final_owner);
    {
        // extract the sword owned by the final owner
        let sword = test_scenario::take_from_sender<Sword>(&mut scenario);
        // verify that the sword has expected properties
        assert!(magic(&sword) == 42 && strength(&sword) == 7, 1);
        // return the sword to the object pool (it cannot be simply "dropped")
        test_scenario::return_to_sender(&mut scenario, sword);
        
    };
    test_scenario::end(scenario);
}

When I run sui move test, it works:

...dir...\my_first_package>sui move test
INCLUDING DEPENDENCY MoveStdlib
INCLUDING DEPENDENCY Sui
BUILDING my_first_package
Running Move unit tests
[ PASS    ] 0x0::my_module::test_sword_transactions
Test result: OK. Total tests: 1; passed: 1; failed: 0

My question is:

For the line let scenario = test_scenario::begin(admin);, scenario is an instance of Scenario and Scenario is a struct from an external module, why are we able to pass a mutable reference of scenario &mut scenario to test_scenario::ctx(&mut scenario) on the line sword_create(42, 7, initial_owner, test_scenario::ctx(&mut scenario));?

My guess is that because test_scenario::ctx(...) and struct Scenario are both defined in the same module, hence according to Privileged Struct Operations, test_scenario::ctx(...) has the privilege to alter/access the fields of struct Scenario.

Thank you.

jymchng avatar Oct 24 '22 08:10 jymchng

Hi @jymchng Thanks for submitting this issue. I have added a few folks to help answer your question. It may take a few days for them to get to it.

randall-Mysten avatar Oct 24 '22 15:10 randall-Mysten

Hi @amnn Could you take a look when you get a chance?

randall-Mysten avatar Nov 18 '22 18:11 randall-Mysten

@jymchng, regarding your question

For the line let scenario = test_scenario::begin(admin);, scenario is an instance of Scenario and Scenario is a struct from an external module, why are we able to pass a mutable reference of scenario &mut scenario to test_scenario::ctx(&mut scenario) on the line sword_create(42, 7, initial_owner, test_scenario::ctx(&mut scenario));?

There is no restriction about which functions can accept mutable references of which other types, but as you pointed out here:

because test_scenario::ctx(...) and struct Scenario are both defined in the same module, hence according to Privileged Struct Operations, test_scenario::ctx(...) has the privilege to alter/access the fields of struct Scenario.

only functions defined in the same module as a type can access fields on that type. Where this becomes relevant is when a reference is forwarded on, like in this example:

// mod_a.move
module example::mod_a {
  struct A { x: u64 };
  public fun new(): A { A { x: 0 } }
  public fun bump(a: &mut A) { *a.x = *a.x + 1; }
}

// mod_b.move
module example::mod_b {
  use example::mod_a;
  public fun forward(a: &mut A) { mod_a::bump(a); }
}

// mod_c.move
module example::mod_c {
  use example::mod_a;
  use example::mod_b;

  public entry fun run() {
    let a = mod_a::new();
    mod_b::forward(&mut a);
  }
}

This code is perfectly legal (and safe), even though a function in mod_b accepts a mutable reference to a type defined in mod_a, because all it can do is pass it on to a function in mod_a that would perform any modification in a safe way.

Thanks for spotting the docs issue as well, that should be fixed as of #5544.

amnn avatar Nov 18 '22 18:11 amnn

Will close this issue as the docs issue has been resolved, and the question now answered. @jymchng, please feel free to re-open if you feel differently.

amnn avatar Dec 05 '22 15:12 amnn