From 87fa51cba2cf1fa3f1375997403d9a290e7a93c3 Mon Sep 17 00:00:00 2001 From: Todd Nowacki Date: Wed, 3 Apr 2024 09:52:17 -0700 Subject: [PATCH] [move reference] Review new docs (#35) * prettier * review * Apply suggestions from code review Co-authored-by: Cam Swords * comments * Fix addr * Comments --------- Co-authored-by: Cam Swords --- .prettierrc | 7 +- reference/README.md | 43 ++- .../src/control-flow/labeled-control-flow.md | 8 +- reference/src/functions.md | 15 + reference/src/index-syntax.md | 66 ++--- reference/src/packages.md | 276 +++++++++--------- reference/src/unit-testing.md | 150 +++++----- 7 files changed, 277 insertions(+), 288 deletions(-) diff --git a/.prettierrc b/.prettierrc index 222861c3..66d4057f 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,9 @@ { "tabWidth": 2, - "useTabs": false + "useTabs": false, + "printWidth": 100, + "semi": true, + "singleQuote": true, + "trailingComma": "all", + "proseWrap": "always" } diff --git a/reference/README.md b/reference/README.md index 6398c472..0ebd6387 100644 --- a/reference/README.md +++ b/reference/README.md @@ -4,35 +4,32 @@ title: Move Book custom_edit_url: https://github.com/move-language/move/edit/main/language/documentation/book/README.md --- -In order to update the Move book and preview changes to it you'll need to -install -[`mdbook`](https://rust-lang.github.io/mdBook/guide/installation.html). You -can do this via `cargo install mdbook`. +In order to update the Move book and preview changes to it you'll need to install +[`mdbook`](https://rust-lang.github.io/mdBook/guide/installation.html). You can do this via +`cargo install mdbook`. -After installing mdbook, you can preview changes via either `mdbook serve`, -or if you want the output to change as you make changes to the book you can -use `mdbook watch`. More information on options can be found at the [mdbook -website](https://rust-lang.github.io/mdBook/). +After installing mdbook, you can preview changes via either `mdbook serve`, or if you want the +output to change as you make changes to the book you can use `mdbook watch`. More information on +options can be found at the [mdbook website](https://rust-lang.github.io/mdBook/). -Once you are happy with your changes to the Move book, you can create a PR to -update the Move book website. This is the process that has been used in -the past and is known to work, but there may be a better way: +Once you are happy with your changes to the Move book, you can create a PR to update the Move book +website. This is the process that has been used in the past and is known to work, but there may be a +better way: -1. Run `mdbook build` in this directory. This will create a directory -called `book`. Copy this to some location `L` outside of the Move git tree. +1. Run `mdbook build` in this directory. This will create a directory called `book`. Copy this to + some location `L` outside of the Move git tree. 2. Make sure your upstream is up-to-date and checkout to `upstream/gh-pages`. -3. Once you have checked out to `upstream/gh-pages`, make sure you are at the -root of the repo. You should see a number of `.html` files. You can now -move all contents in `L` to this location: `mv L/* .` +3. Once you have checked out to `upstream/gh-pages`, make sure you are at the root of the repo. You + should see a number of `.html` files. You can now move all contents in `L` to this location: + `mv L/* .` -4. Once this is done inspect things to make sure the book looks the way you -want. If everything looks good submit a PR to the **`gh-pages`** branch of the -main Move repo. +4. Once this is done inspect things to make sure the book looks the way you want. If everything + looks good submit a PR to the **`gh-pages`** branch of the main Move repo. -After this the normal PR process is followed. Once the PR has been accepted and -lands the updated Move book should be visible immediately. +After this the normal PR process is followed. Once the PR has been accepted and lands the updated +Move book should be visible immediately. -**NB:** When adding a new (sub)section to the book you _must_ include the new -file in the `SUMMARY.md` file otherwise it will not appear in the updated book. +**NB:** When adding a new (sub)section to the book you _must_ include the new file in the +`SUMMARY.md` file otherwise it will not appear in the updated book. diff --git a/reference/src/control-flow/labeled-control-flow.md b/reference/src/control-flow/labeled-control-flow.md index 8f0c6a41..5ca55618 100644 --- a/reference/src/control-flow/labeled-control-flow.md +++ b/reference/src/control-flow/labeled-control-flow.md @@ -37,9 +37,9 @@ fun sum_until_threshold(input: &vector>, threshold: u64): u64 { // the next element we saw would break the threshold, // so we return the current sum break 'outer sum - } + }; j = j + 1; - } + }; i = i + 1; } } @@ -54,10 +54,10 @@ that might see us continuing the inner or outer loop, we could express that code ... 'inner: while (cond) { ... - if (cond0) { break 'outer value } + if (cond0) { break 'outer value }; ... if (cond1) { continue 'inner } - else if (cond2) { continue 'outer } + else if (cond2) { continue 'outer }; ... } ... diff --git a/reference/src/functions.md b/reference/src/functions.md index 4aa65cc9..1a400039 100644 --- a/reference/src/functions.md +++ b/reference/src/functions.md @@ -132,6 +132,21 @@ restrictions are specific to each individual deployment of Move. [The documentation for `entry` functions on Sui can be found here.](https://docs.sui.io/concepts/sui-move-concepts/entry-functions). +To enable easier testing, `entry` functions can be called from +[`#[test]` and `#[test_only]`](./unit-testing.md) contexts. + +```move +module a::m { + entry fun foo(): u64 { 0 } +} +module a::m_test { + #[test] + fun my_test(): u64 { a::m::foo() } // valid! + #[test_only] + fun my_test_helper(): u64 { a::m::foo() } // valid! +} +``` + ### Name Function names can start with letters `a` to `z`. After the first character, function names can diff --git a/reference/src/index-syntax.md b/reference/src/index-syntax.md index 933a9678..84efe2f7 100644 --- a/reference/src/index-syntax.md +++ b/reference/src/index-syntax.md @@ -1,7 +1,7 @@ # Index Syntax Move provides syntax attributes to allow you to define operations that look and feel like native -move code, lowering these operations into your user-provided definitions. +Move code, lowering these operations into your user-provided definitions. Our first syntax method, `index`, allows you to define a group of operations that can be used as custom index accessors for your datatypes, such as accessing a matrix element as `m[i,j]`, by @@ -20,13 +20,13 @@ module matrix { public struct Matrix { v: vector> } #[syntax(index)] - public fun borrow(s: &Matrix, i: u64, j: u64): &T { - borrow(borrow(s.v, i), j) + public fun borrow(s: &Matrix, i: u64, j: u64): &T { + vector::borrow(vector::borrow(&s.v, i), j) } #[syntax(index)] - public fun borrow_mut(s: &mut Matrix, i: u64, j: u64): &mut T { - borrow_mut(borrow_mut(s.v, i), j) + public fun borrow_mut(s: &mut Matrix, i: u64, j: u64): &mut T { + vector::borrow_mut(vector::borrow_mut(&mut s.v, i), j) } public fun make_matrix(v: vector>): Matrix { @@ -39,20 +39,20 @@ module matrix { Now anyone using this `Matrix` type has access to index syntax for it: ```move -let v0 = vector[1, 0, 0]; -let v1 = vector[0, 1, 0]; -let v2 = vector[0, 0, 1]; -let v = vector>[v0, v1, v2]; -let mut m = matrix::make_matrix(v); +let mut m = matrix::make_matrix(vector[ + vector[1, 0, 0], + vector[0, 1, 0], + vector[0, 0, 1], +]); let mut i = 0; while (i < 3) { let mut j = 0; while (j < 3) { if (i == j) { - assert!(m[i, j] == 1, i); + assert!(m[i, j] == 1, 1); } else { - assert!(m[i, j] == 0, i + 10); + assert!(m[i, j] == 0, 0); }; *(&mut m[i,j]) = 2; j = j + 1; @@ -78,13 +78,13 @@ the position and mutable usage of the expression: let mut mat = matrix::make_matrix(...); let m_0_0 = mat[0, 0]; - // translates to copy matrix::borrow(&mat, 0, 0) +// translates to `copy matrix::borrow(&mat, 0, 0)` let m_0_0 = &mat[0, 0]; - // translates to matrix::borrow(&mat, 0, 0) +// translates to `matrix::borrow(&mat, 0, 0)` let m_0_0 = &mut mat[0, 0]; - // translates to matrix::borrow_mut(&mut mat, 0, 0) +// translates to `matrix::borrow_mut(&mut mat, 0, 0)` `` You can also intermix index expressions with field accesses: @@ -95,8 +95,8 @@ public struct V { v: vector } public struct Vs { vs: vector } fun borrow_first(input: &Vs): &u64 { - input.vs[0].v[0] - // translates to vector::borrow(vector::borrow(input.vs, 0).v, 0) + &input.vs[0].v[0] + // translates to `vector::borrow(&vector::borrow(&input.vs, 0).v, 0)` } ```` @@ -111,13 +111,13 @@ structure that takes a default value if the index is out of bounds: #[syntax(index)] public fun borrow_or_set( input: &mut MTable, - key: &Key, + key: Key, default: Value ): &mut Value { - if (contains(input, *key)) { + if (contains(input, key)) { borrow(input, key) } else { - insert(input, *key, default) + insert(input, key, default); borrow(input, key) } } @@ -125,7 +125,7 @@ public fun borrow_or_set( Now, when you index into `MTable`, you must also provide a default value: -``` +```move let string_key: String = ...; let mut table: MTable = m_table::make_table(); let entry: &mut u64 = &mut table[string_key, 0]; @@ -167,9 +167,7 @@ defined in `std::vector` is an example of this: ```move #[syntax(index)] -public fun borrow(v: &vector, i: u64): &Element { - // implementation -} +public native fun borrow(v: &vector, i: u64): ∈ ``` #### Mutable Accessor @@ -180,9 +178,7 @@ element type. The `borrow_mut` function defined in `std::vector` is an example o ```move #[syntax(index)] -public fun borrow_mut(v: &mut vector, i: u64): &mut Element { - // implementation -} +public native fun borrow_mut(v: &mut vector, i: u64): &mut Element; ``` #### Visibility @@ -242,7 +238,7 @@ whose subject and return mutability differ: #[syntax(index)] public fun borrow_imm(x: &mut Matrix, ...): &u64 { ... } // ERROR! incompatible mutability - + // expected a mutable reference '&mut' return type ``` ### Type Compatibility @@ -264,8 +260,8 @@ To illustrate some of these errors, recall the previous `Matrix` definition: ```move #[syntax(index)] -public fun borrow(s: &Matrix, i: u64, j: u64): &T { - borrow(borrow(s.v, i), j) +public fun borrow(s: &Matrix, i: u64, j: u64): &T { + vector::borrow(vector::borrow(&s.v, i), j) } ``` @@ -273,23 +269,23 @@ All of the following are type-incompatible definitions of the mutable version: ```move #[syntax(index)] -public fun borrow_mut(s: &mut Matrix, i: u64, j: u64): &mut T { ... } +public fun borrow_mut(s: &mut Matrix, i: u64, j: u64): &mut T { ... } // ERROR! `T` has `drop` here, but no in the immutable version #[syntax(index)] -public fun borrow_mut(s: &mut Matrix, i: u64, j: u64): &mut u64 { ... } +public fun borrow_mut(s: &mut Matrix, i: u64, j: u64): &mut u64 { ... } // ERROR! This takes a different number of type parameters #[syntax(index)] -public fun borrow_mut(s: &mut Matrix, i: u64, j: u64): &mut U { ... } +public fun borrow_mut(s: &mut Matrix, i: u64, j: u64): &mut U { ... } // ERROR! This takes a different number of type parameters #[syntax(index)] -public fun borrow_mut(s: &mut Matrix, i_j: (u64, u64)): &mut U { ... } +public fun borrow_mut(s: &mut Matrix, i_j: (u64, u64)): &mut U { ... } // ERROR! This takes a different number of arguments #[syntax(index)] -public fun borrow_mut(s: &mut Matrix, i: u64, j: u32): &mut U { ... } +public fun borrow_mut(s: &mut Matrix, i: u64, j: u32): &mut U { ... } // ERROR! `j` is a different type ``` diff --git a/reference/src/packages.md b/reference/src/packages.md index 3750b75a..b8f4fae7 100644 --- a/reference/src/packages.md +++ b/reference/src/packages.md @@ -11,8 +11,8 @@ package system allows programmers to easily: ## Package Layout and Manifest Syntax -A Move package source directory contains a `Move.toml` package manifest file, a -generated `Move.lock` file, and a set of subdirectories: +A Move package source directory contains a `Move.toml` package manifest file, a generated +`Move.lock` file, and a set of subdirectories: ```plaintext a_move_package @@ -24,32 +24,27 @@ a_move_package └── tests (optional, test mode) ``` - The directories and files labeled "required" must be present for a directory to be - considered a Move package and built. Optional directories may be present, - and if so, they will be included in the compilation process depending on the - mode used to build the package. For instance, when built in "dev" or "test" - modes, the `tests` and `examples` directories will also be included. - - Going through each of these in turn: - -1. The `Move.toml` file is the package manifest and is required for a directory - to be considered a Move package. This file contains metadata about the - package, such as name, dependencies, and so on. -2. The `Move.lock` file is generated by the Move CLI and contains the fixed - build versions of the package and its dependencies. It is used to ensure - consistent versions are used across different builds and that changes in - dependencies are apparent as a change in this file. -3. The `sources` directory is required and contains the Move modules that make - up the package. Modules in this directory will always be included in the - compilation process. -4. The `doc_templates` directory can contain documentation templates that will - be used when generating documentation for the package. -5. The `examples` directory can hold additional code to be used only for - development and/or tutorials, this will not be included when compiled - outside of `test` or `dev` modes. -6. The `tests` directory can contain Move modules that are only included when - compiled in `test` mode or when [Move unit tests](./unit-testing.md) are - run. +The directories and files labeled "required" must be present for a directory to be considered a Move +package and built. Optional directories may be present, and if so, they will be included in the +compilation process depending on the mode used to build the package. For instance, when built in +"dev" or "test" modes, the `tests` and `examples` directories will also be included. + +Going through each of these in turn: + +1. The [`Move.toml`](#movetoml) file is the package manifest and is required for a directory to be + considered a Move package. This file contains metadata about the package, such as name, + dependencies, and so on. +1. The [`Move.lock`](#movelock) file is generated by the Move CLI and contains the fixed build + versions of the package and its dependencies. It is used to ensure consistent versions are used + across different builds and that changes in dependencies are apparent as a change in this file. +1. The `sources` directory is required and contains the Move modules that make up the package. + Modules in this directory will always be included in the compilation process. +1. The `doc_templates` directory can contain documentation templates that will be used when + generating documentation for the package. +1. The `examples` directory can hold additional code to be used only for development and/or + tutorials, this will not be included when compiled outside of `test` or `dev` modes. +1. The `tests` directory can contain Move modules that are only included when compiled in `test` + mode or when [Move unit tests](./unit-testing.md) are run. ### Move.toml @@ -157,106 +152,19 @@ MoveStdlib = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/s address_to_be_filled_in = "0x101010101" ``` -Most of the sections in the package manifest are self explanatory, but named -addresses can be a bit difficult to understand so we examine them in more -detail in [Named Addresses During -Compilation](#named-addresses-during-compilation), but before that we'll first -take a look at the `Move.lock` file and what it contains. - -## Move.lock - -The `Move.lock` file is generated at the root of the Move pacakge when the -package is built. The `Move.lock` file contains information about your package -and its build configuration, and acts as a communication layer between the Move -compiler and other tools, like chain-specific command line interfaces and -third-party package managers. - -Like the `Move.toml` file, the `Move.lock` file is a text-based TOML file. -Unlike the package manifest however, the `Move.lock` file is not intended for -you to edit directly. Processes on the toolchain, like the Move compiler, -access and edit the file to read and append relevant information to it. You -also must not move the file from the root, as it needs to be at the same level -as the `Move.toml` manifest in the package. - -If you are using source control for your package, it's recommended practice to -check in the `Move.lock` file that corresponds with your desired built or -published package. This ensures that every build of your package is an exact replica -of the original, and that changes to the build will be apparent as changes to -the `Move.lock` file. - -The `Move.lock` file is a TOML file that currently contains the following -fields. - -**Note**: other fields may be added to the lock file either in the future, or -by third-party package package managers as well. - -### The `[move]` Section - -This section contains the core information needed in the lockfile: -* The version of the lockfile (needed for backwards compatibility checking, and - versioning lockfile changes in the future). -* The hash of the `Move.toml` file that was used to generate this lock file. -* The hash of the `Move.lock` file of all dependencies. If no depencies are - present, this will be an empty string. -* The list of dependencies. - -```ini -[move] -version = # Lock file version, used for backwards compatibility checking. -manifest_digest = # Sha3-256 hash of the Move.toml file that was used to generate this lock file. -deps_digest = # Sha3-256 hash of the Move.lock file of all dependencies. If no depencies are present, this will be an empty string. -dependencies = { (name = )* } # List of dependencies. Not present if there are no dependencies. -``` - -### The `[move.package]` Sections - -After the Move compiler resolves each of the dependencies for the package it -writes the location of the dependency to the `Move.lock` file. If a dependency -failed to resolve, the compiler will not write the `Move.lock` file and the -build fails. If all dependencies resolve, the `Move.lock` file contains the -locations (local and remote) of all of the package's transitive dependencies. -These will be stored in the `Move.lock` file in the following format: - -```ini -# ... - -[[move.package]] -name = "A" -source = { git = "https://github.com/b/c.git", subdir = "e/f", rev = "a1b2c3" } - -[[move.package]] -name = "B" -source = { local = "../local-dep" } -``` - -### The `[move.toolchain-version]` Section - -As mentioned above, additional fields may be added to the lock file by external -tools. For example, the Sui package manager adds toolchain version information -to the lock file that can then be used for on-chain source verification: - -```ini -# ... - -[move.toolchain-version] -compiler-version = # The version of the Move compiler used to build the package, e.g. "1.21.0" -edition = # The edition of the Move language used to build the package, e.g. "2024.alpha" -flavor = # The flavor of the Move compiler used to build the package, e.g. "sui" -``` - -With that, let's now turn to the compilation process and how named addresses are -resolved, and how to use them. +Most of the sections in the package manifest are self explanatory, but named addresses can be a bit +difficult to understand so we examine them in more detail in +[Named Addresses During Compilation](#named-addresses-during-compilation). ## Named Addresses During Compilation -Recall that Move has [named addresses](./primitive-types/address.md) and that named addresses -cannot be declared in Move. Instead they are declared at the package level: in -the manifest file (`Move.toml`) for a Move package you declare named addresses -in the package, instantiate other named addresses, and rename named addresses -from other packages within the Move package system. +Recall that Move has [named addresses](./primitive-types/address.md) and that named addresses cannot +be declared in Move. Instead they are declared at the package level: in the manifest file +(`Move.toml`) for a Move package you declare named addresses in the package, instantiate other named +addresses, and rename named addresses from other packages within the Move package system. -Let's go through each of these actions, and how they are performed in the -package's manifest one-by-one: +Let's go through each of these actions, and how they are performed in the package's manifest +one-by-one: ### Declaring Named Addresses @@ -280,10 +188,10 @@ named_addr = "_" ``` Declares `named_addr` as a named address in the package `example_pkg` and that _this address can be -any valid address value_. In particular, an importing package can pick the value of the named address -`named_addr` to be any address it wishes. Intuitively you can think of this as parameterizing the -package `example_pkg` by the named address `named_addr`, and the package can then be instantiated -later on by an importing package. +any valid address value_. In particular, an importing package can pick the value of the named +address `named_addr` to be any address it wishes. Intuitively you can think of this as +parameterizing the package `example_pkg` by the named address `named_addr`, and the package can then +be instantiated later on by an importing package. `named_addr` can also be declared as: @@ -321,13 +229,13 @@ A named address `N` in a package `P` is in scope if: Additionally, every named address in a package is exported. Because of this and the above scoping rules each package can be viewed as coming with a set of named addresses that will be brought into -scope when the package is imported, e.g., if the `example_pkg` package was imported, that importation -would bring into scope the `named_addr` named address. Because of this, if `P` imports two packages -`P1` and `P2` both of which declare a named address `N` an issue arises in `P`: which "`N`" is meant -when `N` is referred to in `P`? The one from `P1` or `P2`? To prevent this ambiguity around which -package a named address is coming from, we enforce that the sets of scopes introduced by all -dependencies in a package are disjoint, and provide a way to _rename named addresses_ when the -package that brings them into scope is imported. +scope when the package is imported, e.g., if you import `example_pkg`, that import will also bring +the `named_addr` named address into scope. Because of this, if `P` imports two packages `P1` and +`P2` both of which declare a named address `N` an issue arises in `P`: which "`N`" is meant when `N` +is referred to in `P`? The one from `P1` or `P2`? To prevent this ambiguity around which package a +named address is coming from, we enforce that the sets of scopes introduced by all dependencies in a +package are disjoint, and provide a way to _rename named addresses_ when the package that brings +them into scope is imported. Renaming a named address when importing can be done as follows in our `P`, `P1`, and `P2` example above: @@ -363,10 +271,10 @@ instantiated with differing values across the package graph. A Move package can only be compiled if all named addresses resolve to a value. This presents issues if the package wishes to expose an uninstantiated named address. This is what the `[dev-addresses]` -section solves in part. This section can set values for named addresses, but cannot introduce any named -addresses. Additionally, only the `[dev-addresses]` in the root package are included in `dev` mode. -For example a root package with the following manifest would not compile outside of `dev` mode since -`named_addr` would be uninstantiated: +section solves in part. This section can set values for named addresses, but cannot introduce any +named addresses. Additionally, only the `[dev-addresses]` in the root package are included in `dev` +mode. For example a root package with the following manifest would not compile outside of `dev` mode +since `named_addr` would be uninstantiated: ```ini [package] @@ -382,16 +290,15 @@ named_addr = "0xC0FFEE" ## Usage and Artifacts The Move package system comes with a command line option as part of the CLI: -`sui move `. Unless a particular path is provided, all -package commands will run in the current working directory. The full list of -commands and flags for the Move CLI can be found by running `sui move --help`. +`sui move `. Unless a particular path is provided, all package commands +will run in the current enclosing Move package. The full list of commands and flags for the Move CLI +can be found by running `sui move --help`. ### Artifacts -A package can be compiled using CLI commands. -This will create a `build` -directory containing build-related artifacts (such as bytecode, source maps, and -documentation). The general layout of the `build` directory is as follows: +A package can be compiled using CLI commands. This will create a `build` directory containing +build-related artifacts (including bytecode binaries, source maps, and documentation). The general +layout of the `build` directory is as follows: ```plaintext a_move_package @@ -435,3 +342,80 @@ a_move_package    ...    └── *.move ``` + +## Move.lock + +The `Move.lock` file is generated at the root of the Move pacakge when the package is built. The +`Move.lock` file contains information about your package and its build configuration, and acts as a +communication layer between the Move compiler and other tools, like chain-specific command line +interfaces and third-party package managers. + +Like the `Move.toml` file, the `Move.lock` file is a text-based TOML file. Unlike the package +manifest however, the `Move.lock` file is not intended for you to edit directly. Processes on the +toolchain, like the Move compiler, access and edit the file to read and append relevant information +to it. You also must not move the file from the root, as it needs to be at the same level as the +`Move.toml` manifest in the package. + +If you are using source control for your package, it's recommended practice to check in the +`Move.lock` file that corresponds with your desired built or published package. This ensures that +every build of your package is an exact replica of the original, and that changes to the build will +be apparent as changes to the `Move.lock` file. + +The `Move.lock` file is a TOML file that currently contains the following fields. + +**Note**: other fields may be added to the lock file either in the future, or by third-party package +package managers as well. + +### The `[move]` Section + +This section contains the core information needed in the lockfile: + +- The version of the lockfile (needed for backwards compatibility checking, and versioning lockfile + changes in the future). +- The hash of the `Move.toml` file that was used to generate this lock file. +- The hash of the `Move.lock` file of all dependencies. If no dependencies are present, this will be + an empty string. +- The list of dependencies. + +```ini +[move] +version = # Lock file version, used for backwards compatibility checking. +manifest_digest = # Sha3-256 hash of the Move.toml file that was used to generate this lock file. +deps_digest = # Sha3-256 hash of the Move.lock file of all dependencies. If no dependencies are present, this will be an empty string. +dependencies = { (name = )* } # List of dependencies. Not present if there are no dependencies. +``` + +### The `[move.package]` Sections + +After the Move compiler resolves each of the dependencies for the package it writes the location of +the dependency to the `Move.lock` file. If a dependency failed to resolve, the compiler will not +write the `Move.lock` file and the build fails. If all dependencies resolve, the `Move.lock` file +contains the locations (local and remote) of all of the package's transitive dependencies. These +will be stored in the `Move.lock` file in the following format: + +```ini +# ... + +[[move.package]] +name = "A" +source = { git = "https://github.com/b/c.git", subdir = "e/f", rev = "a1b2c3" } + +[[move.package]] +name = "B" +source = { local = "../local-dep" } +``` + +### The `[move.toolchain-version]` Section + +As mentioned above, additional fields may be added to the lock file by external tools. For example, +the Sui package manager adds toolchain version information to the lock file that can then be used +for on-chain source verification: + +```ini +# ... + +[move.toolchain-version] +compiler-version = # The version of the Move compiler used to build the package, e.g. "1.21.0" +edition = # The edition of the Move language used to build the package, e.g. "2024.alpha" +flavor = # The flavor of the Move compiler used to build the package, e.g. "sui" +``` diff --git a/reference/src/unit-testing.md b/reference/src/unit-testing.md index 5f1b683e..23fd6b17 100644 --- a/reference/src/unit-testing.md +++ b/reference/src/unit-testing.md @@ -2,20 +2,19 @@ Unit testing for Move uses three annotations in the Move source language: -- `#[test]` -- `#[test_only]`, and -- `#[expected_failure]`. +- `#[test]` marks a function as a test; +- `#[expected_failure]` marks that a test is expected to fail; +- `#[test_only]` marks a module or module member ([`use`](./uses.md), [function](./functions.md), + [struct](./structs.md), or [constant](./constants.md)) as code to be included for testing only. -They respectively mark a function as a test, mark a module or module member (`use`, function, or -struct) as code to be included for testing only, and mark that a test is expected to fail. These -annotations can be placed on a function with any visibility. Whenever a module or module member is -annotated as `#[test_only]` or `#[test]`, it will not be included in the compiled bytecode unless it -is compiled for testing. +These annotations can be placed on any appropriate form with any visibility. Whenever a module or +module member is annotated as `#[test_only]` or `#[test]`, it will not be included in the compiled +bytecode unless it is compiled for testing. ## Test Annotations -The `#[test]` annotation can only be placed on a function with no parameters. -This annotation marks the function as a test to be run by the unit testing harness. +The `#[test]` annotation can only be placed on a function with no parameters. This annotation marks +the function as a test to be run by the unit testing harness. ```move #[test] // OK @@ -25,12 +24,11 @@ fun this_is_a_test() { ... } fun this_is_not_correct(arg: u64) { ... } ``` -A test can also be annotated as an `#[expected_failure]`. This annotation marks -that the test is expected to raise an error. There are a number of options that -can be used with the `#[expected_failure]` annotation to ensure only a failure -with the specified condition is marked as passing, these options are detailed -in [Expected Failures](#expected-failures). Only functions that have the -`#[test]` annotation can also be annotated as an #`[expected_failure]`. +A test can also be annotated as an `#[expected_failure]`. This annotation marks that the test is +expected to raise an error. There are a number of options that can be used with the +`#[expected_failure]` annotation to ensure only a failure with the specified condition is marked as +passing, these options are detailed in [Expected Failures](#expected-failures). Only functions that +have the `#[test]` annotation can also be annotated as an #`[expected_failure]`. Some simple examples of using the `#[expected_failure]` annotation are shown below: @@ -48,31 +46,33 @@ public fun test_will_error_and_pass() { 1/0; } public fun test_will_error_and_pass_abort_code() { abort ENotFound } #[test] // Will fail since test fails with a different error than expected. -#[expected_failure(abort_code = my_module::EnotFound)] +#[expected_failure(abort_code = my_module::ENotFound)] public fun test_will_error_and_fail() { 1/0; } #[test, expected_failure] // Can have multiple in one attribute. This test will pass. public fun this_other_test_will_abort_and_pass() { abort 1 } ``` +> **Note**: `#[test]` and `#[test_only]` functions can also call +> [`entry`](./functions.md#entry-modifier) functions, regardless of their visibility. + ## Expected Failures -There are a number of different ways that you can use the `#[expected_failure]` -annotation to specify different types of error conditions. These are: +There are a number of different ways that you can use the `#[expected_failure]` annotation to +specify different types of error conditions. These are: ### 1. `#[expected_failure(abort_code = )]` -This will pass if the test aborts with the specified constant value in the -module that defines the constant and fail otherwise. This is the recommended -way of testing for expected test failures. +This will pass if the test aborts with the specified constant value in the module that defines the +constant and fail otherwise. This is the recommended way of testing for expected test failures. -**NOTE**: You can reference constants outside of the current module or package -in `expected_failure` annotations. +> **Note**: You can reference constants outside of the current module or package in +> `expected_failure` annotations. ```move module pkg_addr::other_module { const ENotFound: u64 = 1; - fun will_abort() { + public fun will_abort() { abort ENotFound } } @@ -98,14 +98,13 @@ module pkg_addr::my_module { ### 2. `#[expected_failure(arithmetic_error, location = )]` -This specifies that the test is expected to fail with an arithmetic error -(e.g., integer overflow, division by zero, etc) at the specified location. The -`` must be a valid path to a module location, e.g., `Self`, or -`my_package::my_module`. +This specifies that the test is expected to fail with an arithmetic error (e.g., integer overflow, +division by zero, etc) at the specified location. The `` must be a valid path to a module +location, e.g., `Self`, or `my_package::my_module`. ```move module pkg_addr::other_module { - fun will_arith_error() { 1/0; } + public fun will_arith_error() { 1/0; } } module pkg_addr::my_module { @@ -127,13 +126,13 @@ module pkg_addr::my_module { ### 3. `#[expected_failure(out_of_gas, location = )]` -This specifies that the test is expected to fail with an out of gas error at -the specified location. The `` must be a valid path to a module -location, e.g., `Self`, or `my_package::my_module`. +This specifies that the test is expected to fail with an out of gas error at the specified location. +The `` must be a valid path to a module location, e.g., `Self`, or +`my_package::my_module`. ```move module pkg_addr::other_module { - fun will_oog() { loop {} } + public fun will_oog() { loop {} } } module pkg_addr::my_module { @@ -146,7 +145,8 @@ module pkg_addr::my_module { #[expected_failure(arithmetic_error, location = pkg_addr::other_module)] fun test_will_oog_and_pass2() { other_module::will_oog() } - // FAIL: Will fail since the location we expect it the fail at is different from where the test actually failed. + // FAIL: Will fail since the location we expect it the fail at is different from where + // the test actually failed. #[test] #[expected_failure(out_of_gas, location = Self)] fun test_will_oog_and_fail() { other_module::will_oog() } @@ -155,19 +155,17 @@ module pkg_addr::my_module { ### 4. `#[expected_failure(vector_error, minor_status = , location = )]` -This specifies that the test is expected to fail with a vector error at the -specified location and with the given `minor_status` if provided. The -`` must be a valid path to a module location, e.g., `Self`, or -`my_package::my_module`. The `` is an optional parameter that -specifies the minor status of the vector error. If it is not specified, the -test will pass if the test fails with any minor status. If it is specified, the -test will only pass if the test fails with a vector error with the specified -minor status. +This specifies that the test is expected to fail with a vector error at the specified location with +the given `minor_status` (if provided). The `` must be a valid path to a module module +location, e.g., `Self`, or `my_package::my_module`. The `` is an optional parameter that +specifies the minor status of the vector error. If it is not specified, the test will pass if the +test fails with any minor status. If it is specified, the test will only pass if the test fails with +a vector error with the specified minor status. ```move module pkg_addr::other_module { - fun vector_borrow_empty() { - vector::borrow(&vector::empty(), 1); + public fun vector_borrow_empty() { + &vector[][1]; } } @@ -175,7 +173,7 @@ module pkg_addr::my_module { #[test] #[expected_failure(vector_error, location = Self)] fun vector_abort_same_module() { - vector::borrow(&vector::empty(), 1); + vector::borrow(&vector[], 1); } #[test] @@ -188,7 +186,7 @@ module pkg_addr::my_module { #[test] #[expected_failure(vector_error, minor_status = 1, location = Self)] fun native_abort_good_right_code() { - vector::borrow(&vector::empty(), 1); + vector::borrow(&vector[], 1); } // FAIL: correct error, but wrong location. @@ -202,17 +200,16 @@ module pkg_addr::my_module { #[test] #[expected_failure(vector_error, minor_status = 0, location = Self)] fun vector_abort_wrong_minor_code() { - vector::borrow(&vector::empty(), 1); + vector::borrow(&vector[], 1); } } ``` ### 5. `#[expected_failure]` -This will pass if the test aborts with any error code. Because of this you -should be incredibly careful using this way of annotating expected tests -failures, and instead prefer one of the ways described above instead. Examples -of these types of annotations are: +This will pass if the test aborts with _any_ error code. You should be **_incredibly careful_** +using this to annotate expected tests failures, and always prefer one of the ways described above +instead. Examples of these types of annotations are: ```move #[test] @@ -224,25 +221,23 @@ fun test_will_abort_and_pass1() { abort 1 } fun test_will_arith_error_and_pass2() { 1/0; } ``` - ## Test Only Annotations -A module and any of its members can be declared as test only. If an item is -annotated as `#[test_only]` the item will only be included in the compiled Move -bytecode when compiled in test mode. Additionally, when compiled outside of -test mode, any non-test `use`s of a `#[test_only]` module will raise an error -during compilation. +A module and any of its members can be declared as test only. If an item is annotated as +`#[test_only]` the item will only be included in the compiled Move bytecode when compiled in test +mode. Additionally, when compiled outside of test mode, any non-test `use`s of a `#[test_only]` +module will raise an error during compilation. -**NOTE**: functions that are annotated with `#[test_only]` will only be available -to be called from test code, but they themselves are not tests and will not be -run as tests by the unit testing framework. +> **Note**: functions that are annotated with `#[test_only]` will only be available to be called +> from test code, but they themselves are not tests and will not be run as tests by the unit testing +> framework. ```move #[test_only] // test only attributes can be attached to modules module abc { ... } #[test_only] // test only attributes can be attached to constants -const Addr: address = @0x1; +const MY_ADDR: address = @0x1; #[test_only] // .. to uses use pkg_addr::some_other_module; @@ -262,17 +257,15 @@ When running tests, every test will either `PASS`, `FAIL`, or `TIMEOUT`. If a te location of the failure along with the function name that caused the failure will be reported if possible. You can see an example of this below. -A test will be marked as timing out if it exceeds the maximum number of -instructions that can be executed for any single test. This bound can be -changed using the options below. Additionally, while the result of a test is -always deterministic, tests are run in parallel by default, so the ordering of -test results in a test run is non-deterministic unless running with only one -thread (see `OPTIONS` below on how to do this). +A test will be marked as timing out if it exceeds the maximum number of instructions that can be +executed for any single test. This bound can be changed using the options below. Additionally, while +the result of a test is always deterministic, tests are run in parallel by default, so the ordering +of test results in a test run is non-deterministic unless running with only one thread, which can be +configured via an option. -There are also a number of options that can be passed to the unit testing binary to fine-tune -testing and to help debug failing tests. The available options, and a -description of what each one can do can be found by passing the help flag to -the `sui move test` command: +These aforementioned options are two among many that can fine-tune testing and help debug failing +tests. To see all available options, and a description of what each one does, pass the `--help` flag +to the `sui move test` command: ``` $ sui move test --help @@ -352,9 +345,9 @@ Test result: OK. Total tests: 3; passed: 3; failed: 0 #### Passing specific tests to run -You can run a specific test, or a set of tests with `sui move test `. This -will only run tests whose fully qualified name contains ``. For example if -we wanted to only run tests with `"non_zero"` in their name: +You can run a specific test, or a set of tests with `sui move test `. This will only run tests +whose fully qualified name contains ``. For example if we wanted to only run tests with +`"non_zero"` in their name: ```bash $ sui move test non_zero @@ -419,10 +412,9 @@ Test result: FAILED. Total tests: 3; passed: 0; failed: 3 #### `-s` or `--statistics` -With these flags you can gather statistics about the tests run and report the -runtime and gas used for each test. You can additionally add `csv` (`sui move -test -s csv`) to get the gas usage in a csv output format. For example, if we -wanted to see the statistics for the tests in the example above: +With these flags you can gather statistics about the tests run and report the runtime and gas used +for each test. You can additionally add `csv` (`sui move test -s csv`) to get the gas usage in a csv +output format. For example, if we wanted to see the statistics for the tests in the example above: ```bash $ sui move test -s