0.3.2 2022年8月23日
0.3.1 2022年8月13日
0.2.1 2022年7月22日
0.1.6 2022年7月5日
0.1.4 2022年5月23日

#55 in #覆盖率

Download history 44/week @ 2024-04-02 2/week @ 2024-04-16 5/week @ 2024-04-23 3/week @ 2024-04-30 4/week @ 2024-05-14 15/week @ 2024-05-21 1/week @ 2024-05-28 4/week @ 2024-06-04 6/week @ 2024-06-11 18/week @ 2024-06-18 2/week @ 2024-06-25 89/week @ 2024-07-02

91 每月下载次数
用于 111 个crate(4 直接)

Apache-2.0

2MB
39K SLoC

Move CLI

Move命令行界面(Move CLI)是一个工具,它提供了与Move交互的简单方式,可以用来编写和运行Move代码,并实验开发用于Move开发的新工具。为了反映这一点,Move CLI命令被分为三个主要子命令

  • 包命令:是创建、编译和测试Move包以及执行与包相关的其他操作的命令。这些命令不依赖于Move适配器实现或存储实现。
  • 沙盒命令:是允许您编写Move模块和脚本、编写和运行脚本和测试以及查看在本地沙盒环境中的执行结果的命令。
  • 实验命令:是当前正在开发的实验性命令。

除了package create之外,每个Move CLI命令都应在Move包的上下文中运行。

安装

$ cargo install --path move/language/tools/move-cli

$ cargo install --git https://github.com/move-language/move move-cli --branch main

这将把move二进制文件安装到您的Cargo二进制目录中。在macOS和Linux上,这通常是~/.cargo/bin。您需要确保此位置在您的PATH环境变量中。

现在您应该可以运行Move CLI了

$ move
Move 0.1.0
CLI frontend for Move compiler and VM

USAGE:
    move [FLAGS] [OPTIONS] <SUBCOMMAND>
  ...

在这里,我们将介绍最常用的Move CLI命令和标志。然而,您可以通过调用move --help来找到可用的完整命令列表。此外,您可以通过向每个Move CLI命令传递--help标志来找到可用于每个命令的完整标志和选项列表,即move <command> --help

包命令

包命令提供了围绕其他命令的包装器,这些命令由各种Move工具、编译器或验证器提供。

move new命令将创建一个新的空Move包

$ move new <package_name> # Create a Move package <package_name> under the current dir
$ move new <package_name> -p <path> # Create a Move package <package_name> under path <path>

在包的根目录中,您可以使用以下命令构建包中编写的模块和/或脚本

$ move build # Builds the Move package you are currently in
$ move build -p <path> # Builds the Move package at <path>

编译后的工件默认存储在build目录中。您可以通过传递可选的--build-dir标志来更改构建工件保存的位置

$ move build --build-dir <path_to_save_to> # Build current Move package and save artifacts under <path_to_save_to>

您可以使用prove命令使用Move Prover验证Move包中的规范

$ move prove # Verify the specifications in the current package
$ move prove -p <path> # Verify the specifications in the package at <path>

为了运行Move Prover,需要安装其他工具。有关Move Prover及其配置选项的信息可以在此处此处找到。

您还可以使用test命令在包中运行单元测试

$ move test # Run Move unit tests in the current package
$ move test -p <path> # Run Move unit tests in the package at <path>

沙盒命令

沙盒允许您在不使用验证器、区块链或交易的情况下编写和运行Move代码。持久数据以模拟Move内存模型的目录结构存储在磁盘上。

项目结构

每个沙盒命令都在Move包的上下文中运行。因此,让我们创建一个Move包,我们将使用此README中的代码,并使用cd进入它

$ move new readme
$ cd readme

编译和运行脚本

让我们从一个简单的脚本开始,该脚本打印其signer。创建一个名为sources/debug_script.move的文件,并将以下内容输入其中

// sources/debug_script.move
script {
use std::debug;
fun debug_script(account: signer) {
    debug::print(&account)
}
}

然而,在运行此脚本之前,我们需要导入Move标准库nursery,以便访问Debug模块和Std 命名地址。您可以在本地指定依赖项,或使用Git URL。在这里,我们将使用Git指定它,因此将以下内容添加到Move.toml文件中的readme目录

[addresses]
std = "0x1" # Specify and assign 0x1 to the named address "std"

[dependencies]
MoveNursery = { git = "https://github.com/move-language/move.git", subdir = "language/move-stdlib/nursery", rev = "main" }
#                ^                    ^                              ^                                       ^
#            Git dependency       Git clone URL       Subdir under git repo (optional)           Git revision to use

现在让我们尝试运行脚本--第一次可能需要一些时间,因为包命令将克隆给定的URL中的存储库,但后续调用应该很快

$ move sandbox run sources/debug_script.move --signers 0xf
[debug] (&) { 0000000000000000000000000000000F }

--signers 0xf参数指示哪些账户地址已签署该脚本。省略--signers或将多个签署者传递给此单个signer脚本将触发类型错误。

传递参数

CLI支持通过--args将非signer参数传递给move sandbox run。支持以下参数类型

  • bool字面量(truefalse
  • u64 文字(例如,1058
  • address 文字(例如,0x120x0000000000000000000000000000000f
  • 十六进制字符串(例如,'x"0012"' 将解析为 vector<u8>[00, 12]
  • ASCII 字符串(例如,'b"hi"' 将解析为 vector<u8>[68, 69]

发布新模块

在执行事务脚本时,您通常会调用不同的 Move 模块,如上面的示例中使用 Debug 模块。可以在 CLI 被调用的包的 sources 目录中添加新模块。您还可以添加对其他包的依赖,以便访问它们定义的模块(就像我们上面使用 Debug 模块所做的那样)。move 沙盒运行 命令将编译并发布包中的每个模块以及每个包的传递依赖中的模块,然后再运行指定的脚本。

尝试将此代码保存到 sources/Test.move

// sources/Test.move
module 0x2::Test {
    use std::signer;

    struct Resource has key { i: u64 }

    public fun publish(account: &signer) {
        move_to(account, Resource { i: 10 })
    }

    public fun write(account: &signer, i: u64) acquires Resource {
        borrow_global_mut<Resource>(signer::address_of(account)).i = i;
    }

    public fun unpublish(account: &signer) acquires Resource {
        let Resource { i: _ } = move_from(signer::address_of(account));
  }
}

现在,尝试

$ move build

这将导致 CLI 编译和类型检查 sources 下的模块,但不会在 storage 下发布模块字节码。您可以通过运行 move 沙盒发布 命令来编译并发布模块(这里我们传递了 -v 或详细标志以更好地了解正在发生的事情)

$ move sandbox publish -v
Found 1 modules
Publishing a new module 00000000000000000000000000000002::Test (wrote 253 bytes)
Wrote 253 bytes of module ID's and code

现在,如果我们查看 storage,我们将看到已发布的 Test 模块的字节码

$ ls storage/0x00000000000000000000000000000002/modules
Test.mv

我们还可以使用 move 沙盒查看 来检查存储中的编译后的字节码

$ move sandbox view storage/0x00000000000000000000000000000002/modules/Test.mv
module 2.Test {
struct Resource has key {
  i: u64
}

public publish() {
  0: MoveLoc[0](Arg0: &signer)
  1: LdU64(10)
  2: Pack[0](Resource)
  3: MoveTo[0](Resource)
  4: Ret
}
public unpublish() {
  0: MoveLoc[0](Arg0: &signer)
  1: Call[3](address_of(&signer): address)
  2: MoveFrom[0](Resource)
  3: Unpack[0](Resource)
  4: Pop
  5: Ret
}
public write() {
  0: CopyLoc[1](Arg1: u64)
  1: MoveLoc[0](Arg0: &signer)
  2: Call[3](address_of(&signer): address)
  3: MutBorrowGlobal[0](Resource)
  4: MutBorrowField[0](Resource.i: u64)
  5: WriteRef
  6: Ret
}
}

您还可以通过运行 move 反汇编 --name <module_name>move 反汇编 --name <module_name> --interactive 来在发布到 storage 之前查看编译后的字节码,并交互式地检查字节码及其与 Move 源代码的关系

$ move disassemble --name Test --interactive # You can quit by pressing "q"
$ move disassemble --name Test
// Move bytecode v4
module 2.Test {
struct Resource has key {
        i: u64
}

public publish() {
B0:
        0: MoveLoc[0](account: &signer)
        1: LdU64(10)
        2: Pack[0](Resource)
        3: MoveTo[0](Resource)
        4: Ret
}
public unpublish() {
B0:
        0: MoveLoc[0](account: &signer)
        1: Call[3](address_of(&signer): address)
        2: MoveFrom[0](Resource)
        3: Unpack[0](Resource)
        4: Pop
        5: Ret
}
public write() {
B0:
        0: CopyLoc[1](i: u64)
        1: MoveLoc[0](account: &signer)
        2: Call[3](address_of(&signer): address)
        3: MutBorrowGlobal[0](Resource)
        4: MutBorrowField[0](Resource.i: u64)
        5: WriteRef
        6: Ret
}
}

更新状态

让我们通过运行以下脚本来练习我们的新 Test 模块

// sources/test_script.move
script {
use 0x2::Test;
fun test_script(account: signer) {
    Test::publish(&account)
}
}

本脚本调用我们 Test 模块中的 publish 函数,该函数将在签名人账户下发布一个类型为 Test::Resource 的资源。让我们先看看在没有提交这些更改之前,这个脚本会做出哪些改变。我们可以通过传递 --dry-run 标志来实现这一点

$ move sandbox run sources/test_script.move --signers 0xf -v --dry-run
Compiling transaction script...
Changed resource(s) under 1 address(es):
  Changed 1 resource(s) under address 0000000000000000000000000000000F:
    Added type 0x2::Test::Resource: [10, 0, 0, 0, 0, 0, 0, 0] (wrote 40 bytes)
Wrote 40 bytes of resource ID's and data
      key 0x2::Test::Resource {
           i: 10
      }
Discarding changes; re-run without --dry-run if you would like to keep them.

一切看起来都很正常,因此我们可以再次运行这个脚本,但这次需要移除 --dry-run 标志来提交更改

$ move sandbox run sources/test_script.move --signers 0xf -v
Compiling transaction script...
Changed resource(s) under 1 address(es):
  Changed 1 resource(s) under address 0000000000000000000000000000000F:
    Added type 0x2::Test::Resource: [10, 0, 0, 0, 0, 0, 0, 0] (wrote 40 bytes)
Wrote 40 bytes of resource ID's and data
      key 0x2::Test::Resource {
            i: 10
      }

虽然上面使用的详细标志(-v)显示了资源更改,但也可以手动查看它们。由于更改已经提交,我们可以使用 move sandbox view 来检查新发布的资源

$ move sandbox view storage/0x0000000000000000000000000000000F/resources/0x00000000000000000000000000000002::Test::Resource.bcs
key 0x2::Test::Resource {
    i: 10
}

清理状态

由于状态从一个 Move CLI 调用持续到另一个调用,因此你经常希望从一个干净的状态开始。这可以通过使用 move sandbox clean 命令来实现,该命令将删除 storagebuild 目录

$ move sandbox view storage/0x0000000000000000000000000000000F/resources/0x00000000000000000000000000000002::Test::Resource.bcs
resource 0x2::Test::Resource {
        i: 10
}
$ move sandbox clean
$ move sandbox view storage/0x0000000000000000000000000000000F/resources/0x00000000000000000000000000000002::Test::Resource.bcs
Error: `move sandbox view <file>` must point to a valid file under storage

使用 Move CLI 进行预期值测试

如前所述,Move 有一个单元测试框架。然而,单元测试不能测试一切 - 特别是测试事件不能轻松完成。为了帮助编写需要检查事件和期望特定状态的测试,Move CLI 还有一个内置的预期值测试框架。每个测试都在其自己的沙盒中独立运行,因此状态不会从一个测试持续到另一个测试。

每个测试都结构为一个 Move 包,以及一个额外的 args.txt 文件,该文件指定了在该目录中应运行的 Move CLI 命令序列。此外,还必须有一个 args.exp 文件,其中包含运行 args.txt 文件中指定的 Move CLI 命令序列的预期输出。

例如,如果我们想创建一个重新运行我们迄今为止看到的所有命令的 Move CLI 测试,我们可以在我们最初创建并添加脚本和模块的 readme 目录中添加一个 args.txt

readme/
├── args.txt
├── Move.toml
└── sources
    ├── debug_script.move
    ├── Test.move
    └── test_script.move

并且,其中 args.txt 文件包含以下 Move CLI 命令

$ cd ..
$ cat readme/args.txt
## Arg files can have comments!
sandbox run sources/debug_script.move --signers 0xf
sandbox run sources/debug_script.move --signers 0xf
build
sandbox publish
sandbox view storage/0x00000000000000000000000000000002/modules/Test.mv
sandbox run sources/test_script.move --signers 0xf -v
sandbox view storage/0x0000000000000000000000000000000F/resources/0x00000000000000000000000000000002::Test::Resource.bcs

然后,我们可以使用 move sandbox test 命令并指向 readme 目录来运行这些 Move CLI 命令的每个命令序列

$ move sandbox exp-test -p readme
...<snipped output>
0 / 1 test(s) passed.
Error: 1 / 1 test(s) failed.

然而,正如我们所看到的,这个测试会失败,因为没有为测试创建 args.exp 文件。我们可以通过在运行测试时设置 UPDATE_BASELINE 环境变量来生成这个预期文件

$ UPDATE_BASELINE=1 move sandbox exp-test -p readme
1 / 1 test(s) passed.

现在应该在 readme 目录下有一个 args.exp 文件,其中包含运行 args.txt 文件中指定的 Move CLI 命令序列的预期输出

$ cat readme/args.exp
Command `sandbox run sources/debug_script.move --signers 0xf`:
[debug] (&) { 0000000000000000000000000000000F }
Command `sandbox run sources/debug_script.move --signers 0xf --mode bare`:
...

使用代码覆盖率跟踪进行测试

代码覆盖率一直是软件测试中的一个重要指标。在 Move CLI 预期值测试中,我们通过传递给 move sandbox exp-test 命令的额外标志 --track-cov 来解决对代码覆盖率信息的需要。

注意:要查看覆盖率信息,Move CLI 必须安装时带上 --debug 标志;即,cargo install --debug --path move/language/tools/move-cli.

以下通过运行示例进行说明

$ move sandbox exp-test -p readme --track-cov
1 / 1 test(s) passed.
Module 00000000000000000000000000000002::Test
        fun publish
                total: 5
                covered: 5
                % coverage: 100.00
        fun unpublish
                total: 6
                covered: 0
                % coverage: 0.00
        fun write
                total: 7
                covered: 0
                % coverage: 0.00
>>> % Module coverage: 27.78

输出表明不仅测试通过,而且 publish 函数观察到 100% 的指令覆盖率。这是预期的,因为我们的 test_script.move 整个目的就是运行 publish 函数。同时,其他两个函数 unpublishwrite 永远没有被执行,使得整个 Test 模块的平均覆盖率仅为 27.78%。

内部,Move CLI 使用 Move VM 提供的跟踪功能来记录编译的字节码中哪些指令被执行,并使用这些信息来计算代码覆盖率。在 Move 中,指令覆盖率通常可以满足常见 C/C++/Rust 覆盖率跟踪工具中的行覆盖率的作用。

注意,覆盖率信息是汇总在 args.txt 中多个 run 命令的。为了说明这一点,假设我们在 readme/sources 下还有一个名为 test_unpublish_script.move 的另一个测试脚本,内容如下

script {
use 0x2::Test;
fun test_unpublish_script(account: signer) {
    Test::unpublish(&account)
}
}

我们进一步将新命令添加到 args.txt 的末尾(args.exp 也需要更新)。

sandbox run sources/test_unpublish_script.move --signers 0xf -v

现在我们可以再次测试 readme

$ move sandbox exp-test -p readme --track-cov
1 / 1 test(s) passed.
Module 00000000000000000000000000000002::Test
        fun publish
                total: 5
                covered: 5
                % coverage: 100.00
        fun unpublish
                total: 6
                covered: 6
                % coverage: 100.00
        fun write
                total: 7
                covered: 0
                % coverage: 0.00
>>> % Module coverage: 61.11

这次,请注意 unpublish 函数的覆盖率也达到了 100%,整体模块覆盖率提升到了 61.11%。

检测破坏性更改

move sandbox publish 命令会自动检测升级模块时可能导致的破坏性更改。破坏性更改有两种类型

  • 链接兼容性(例如,删除或更改其他模块调用的公共函数的签名,删除其他模块使用的结构体或资源类型)
  • 布局兼容性(例如,添加/删除资源或结构体字段)

move sandbox publish 执行的破坏性更改分析必然是保守的。例如,假设我们 move sandbox publish 以下模块

address 0x2 {
module M {
    struct S has key { f: u64, g: u64 }
}
}

然后希望将其升级到以下版本

address 0x2 {
module M {
    struct S has key { f: u64 }
}
}

在新的版本上运行 move sandbox publish 将会失败

Breaking change detected--publishing aborted. Re-run with --ignore-breaking-changes to publish anyway.
Error: Layout API for structs of module 00000000000000000000000000000002::M has changed. Need to do a data migration of published structs

在这种情况下,我们知道我们没有在全局存储中发布任何 S 的实例,所以可以安全地重新运行 move sandbox publish --ignore-breaking-changes(如推荐)。我们可以通过运行 move sandbox doctor 来双重检查这不是破坏性更改。这个方便的命令会对全局存储进行彻底的健全性检查,以检测过去是否发生了任何破坏性更改

  • 所有模块都通过了字节码验证器
  • 所有模块都与其依赖项链接
  • 所有资源都根据其声明的类型反序列化
  • 所有事件都根据其声明的类型反序列化

依赖关系

~34–51MB
~856K SLoC