#cargo-toml #cargo #testing #cargo-test #testing-tools #cargo-subcommand #cargo-manifest

dev bin+lib cargo-tarpaulin

Cargo-Tarpaulin 是一个通过测试确定代码覆盖率的工具

110 个版本 (29 个破坏性更新)

新版本 0.31.2 2024年8月20日
0.31.0 2024年7月22日
0.27.3 2024年1月13日
0.27.2 2023年11月28日
0.3.9 2017年7月24日

#19 in Cargo插件

Download history 7589/week @ 2024-04-30 8023/week @ 2024-05-07 8266/week @ 2024-05-14 6541/week @ 2024-05-21 6696/week @ 2024-05-28 7666/week @ 2024-06-04 6469/week @ 2024-06-11 7234/week @ 2024-06-18 6564/week @ 2024-06-25 6077/week @ 2024-07-02 6435/week @ 2024-07-09 6572/week @ 2024-07-16 7215/week @ 2024-07-23 7169/week @ 2024-07-30 7544/week @ 2024-08-06 7064/week @ 2024-08-13

每月30,383次下载
用于 18 个crate

MIT/Apache

565KB
11K SLoC

Tarpaulin

Build Status Latest Version License:MIT Docker Developers Wiki Coverage Status

Tarpaulin 是一个为 Cargo 构建系统提供的代码覆盖率报告工具,以用于覆盖船上货物的防水布命名。

目前,Tarpaulin 提供了工作行覆盖率,尽管相当可靠,但结果可能仍含有一些小错误。已经做了大量工作使其能够在广泛的项目上运行,但独特的包和构建功能组合可能导致问题,因此请报告您发现的所有错误。此外,请查看我们的路线图以了解计划中的功能。

在 Linux 上,Tarpaulin 的默认跟踪后端仍然是 Ptrace,并且仅在 x86_64 处理器上工作。可以使用 --engine llvm 将其更改为 llvm 覆盖率工具。对于 Mac 和 Windows,这是默认收集方法。

它也可以在 Docker 中运行,这对于您不使用 Linux 但想在本地运行它很有用,例如在开发期间。下面是如何做到这一点的说明。

以下是对可用标志和功能的详细解释的帮助文本

Cargo-Tarpaulin is a tool to determine code coverage achieved via tests

Usage: cargo tarpaulin [OPTIONS] [-- <ARGS>...]

Arguments:
  [ARGS]...  Arguments to be passed to the test executables can be used to filter or skip certain tests

Options:
      --print-rust-flags           Print the RUSTFLAGS options that tarpaulin will compile your program with and exit
      --print-rustdoc-flags        Print the RUSTDOCFLAGS options that tarpaulin will compile any doctests with and exit
      --color <WHEN>               Coloring: auto, always, never [possible values: Auto, Always, Never]
      --debug                      Show debug output - this is used for diagnosing issues with tarpaulin
  -v, --verbose                    Show extra output
      --dump-traces                Log tracing events and save to a json file. Also, enabled when --debug is used
      --run-types <TYPE>           Type of the coverage run [possible values: Tests, Doctests, Benchmarks, Examples, Lib, Bins, AllTargets]
      --benches                    Test all benches
      --doc                        Test only this library's documentation
      --all-targets                Test all targets (excluding doctests)
      --lib                        Test only this package's library unit tests
      --bins                       Test all binaries
      --examples                   Test all examples
      --tests                      Test all tests
      --config <FILE>              Path to a toml file specifying a list of options this will override any other options set
      --ignore-config              Ignore any project config files
      --bin [<NAME>...]            Test only the specified binary
      --example [<NAME>...]        Test only the specified example
      --test [<NAME>...]           Test only the specified test target
      --bench [<NAME>...]          Test only the specified bench target
      --no-fail-fast               Run all tests regardless of failure
      --profile <NAME>             Build artefacts with the specified profile
      --ignore-tests               Ignore lines of test functions when collecting coverage (default)
      --no-dead-code               Stops tarpaulin from building projects with -Clink-dead-code
      --include-tests              Include lines of test functions when collecting coverage
      --ignore-panics              Ignore panic macros in tests
      --count                      Counts the number of hits during coverage
  -i, --ignored                    Run ignored tests as well
  -l, --line                       Line coverage
      --skip-clean                 The opposite of --force-clean
      --force-clean                Adds a clean stage to work around cargo bugs that may affect coverage results
      --fail-under <PERCENTAGE>    Sets a percentage threshold for failure ranging from 0-100, if coverage is below exit with a non-zero code
  -b, --branch                     Branch coverage: NOT IMPLEMENTED
  -f, --forward                    Forwards unexpected signals to test. This is now the default behaviour
      --coveralls <KEY>            Coveralls key, either the repo token, or if you're using travis use $TRAVIS_JOB_ID and specify travis-{ci|pro} in --ciserver
      --report-uri <URI>           URI to send report to, only used if the option --coveralls is used
      --no-default-features        Do not include default features
      --features [<FEATURES>...]   Features to be included in the target project
      --all-features               Build all available features
      --all                        Alias for --workspace (deprecated)
      --workspace                  Test all packages in the workspace
  -p, --packages [<PACKAGE>...]    Package id specifications for which package should be build. See cargo help pkgid for more info
  -e, --exclude [<PACKAGE>...]     Package id specifications to exclude from coverage. See cargo help pkgid for more info
      --exclude-files [<FILE>...]  Exclude given files from coverage results has * wildcard
  -t, --timeout <SECONDS>          Integer for the maximum time in seconds without response from test before timeout (default is 1 minute)
      --post-test-delay <SECONDS>  Delay after test to collect coverage profiles
      --follow-exec                Follow executed processes capturing coverage information if they're part of your project
      --release                    Build in release mode
      --no-run                     Compile tests but don't run coverage
      --implicit-test-threads      'Don't supply an explicit `--test-threads` argument to test executable. By default tarpaulin will infer the default rustc would pick if not ran via tarpaulin and set it
      --locked                     Do not update Cargo.lock
      --frozen                     Do not update Cargo.lock or any caches
      --target <TRIPLE>            Compilation target triple
      --target-dir <DIR>           Directory for all generated artifacts
      --offline                    Run without accessing the network
      --avoid-cfg-tarpaulin        Remove --cfg=tarpaulin from the RUSTFLAG
  -j, --jobs <N>                   Number of parallel jobs, defaults to # of CPUs
      --rustflags <FLAGS>          Rustflags to add when building project (can also be set via RUSTFLAGS env var)
      --objects [<objects>...]     Other object files to load which contain information for llvm coverage - must have been compiled with llvm coverage instrumentation (ignored for ptrace)
  -Z [<FEATURES>...]               List of unstable nightly only flags
  -o, --out [<FMT>...]             Output format of coverage report [possible values: Json, Stdout, Xml, Html, Lcov]
      --engine <ENGINE>            Coverage tracing backend to use [possible values: Auto, Ptrace, Llvm]
      --output-dir <PATH>          Specify a custom directory to write report files
      --command <CMD>              cargo subcommand to run. So far only test and build are supported [possible values: Test, Build]
  -r, --root <DIR>                 Calculates relative paths to root directory. If --manifest-path isn't specified it will look for a Cargo.toml in root
      --manifest-path <PATH>       Path to Cargo.toml
      --ciserver <SERVICE>         CI server being used, if unspecified tarpaulin may automatically infer for coveralls uploads
      --fail-immediately           Option to fail immediately after a single test fails
  -h, --help                       Print help
  -V, --version                    Print version

关于使用信号的测试的说明

如果您的测试或应用程序使用了 Unix 信号,它们可能与 Tarpaulin 中的 ptrace 工具不兼容。这是因为 Tarpaulin 依赖于 sigtrap 信号来捕获工具点被击中时的情况。--forward 选项将导致将非由 SIGSTOP、SIGSEGV 或 SIGILL 引起的进程停止的信号从测试二进制文件转发。

LLVM 覆盖率的细微差别

尽管通常更准确,但 LLVM 覆盖率工具仍然存在一些细微差别。

  1. 如果测试有一个非零的退出代码,则不会返回覆盖率数据
  2. 一些线程不安全的区域
  3. 无法处理fork和类似的系统调用(一个进程将覆盖另一个进程的profraw文件)

在这种情况下,ptrace和llvm之间的覆盖率结果可能会有很大差异,并且llvm覆盖率可能不是更好的选择。例如,具有should_panic属性或--no-fail-fast的文档测试将不会报告任何覆盖率,因为这些测试具有非零的退出代码。如果您使用这些测试并希望从它们中获得覆盖率数据,则应避免使用llvm覆盖率后端。

功能

以下是目前已实现的特性列表。由于Tarpaulin将二进制文件加载到内存中并解析调试信息,不同的设置可能导致覆盖率不起作用。在这种情况下,请提出一个问题,详细说明您的设置和一个示例项目,我将尝试修复它(请链接到存储库和包含您的项目的提交,并粘贴详细的输出)。

  • 行覆盖率
  • 与cargo test CLI参数完全兼容
  • 上传覆盖率到https://coveralls.iohttps://codecov.io
  • 生成HTML报告和其他覆盖率报告类型
  • 支持测试、文档测试、基准测试和示例的覆盖率
  • 从覆盖率中排除无关文件
  • 配置文件用于互斥覆盖率设置(有关详细信息,请参阅配置文件部分)

问题和贡献

问题和功能请求以及拉取请求始终欢迎!有关如何处理Tarpaulin中发现的错误和添加功能的指南,请参阅CONTRIBUTING。如果您遇到任何问题,也请参阅我们的故障排除

Rust 1.23在编译器中引入了一个回归,影响了Tarpaulin的准确性。如果您看到缺失的行或文件,请检查您的编译器版本。

使用方法

安装

Tarpaulin是一个命令行程序,您可以使用cargo install将其安装到您的开发环境中

cargo install cargo-tarpaulin

当使用Nix软件包管理器时,可以使用nixpkgs.cargo-tarpaulin软件包。这确保Tarpaulin将与您其他软件包相同的rust版本一起构建。

您还可以使用cargo-binstall

cargo binstall cargo-tarpaulin

环境变量

当Tarpaulin运行您的测试时,它会努力在类似于通过cargo test运行的测试环境中运行它们。为了实现这一点,当执行测试二进制文件时,它将设置以下环境变量

  • RUST_BACKTRACE - 当使用--verbose标志时
  • CARGO_MANIFEST_DIR - Cargo.toml的路径,来自--root | --manifest-path或从当前或父目录猜测
  • CARGO_PKG_NAME - 来自Cargo.toml
  • CARGO_PKG_AUTHORS - 来自Cargo.toml
  • CARGO_PKG_VERSION - 来自Cargo.toml
  • LLVM_PROFILE_FILE - 用于LLVM覆盖率

Cargo清单

为了正确构造Cargo环境,Tarpaulin需要通过以下方式之一找到Cargo.toml

  • 使用--root--manifest-path
  • 在包含Cargo.toml清单的项目的工作目录中调用Cargo
  • 从项目的子目录中调用Cargo

如果Cargo无法通过上述任何一种方法找到任何Cargo.toml,运行将错误“cargo metadata”并退出。

在rust-lang中打开了几个RFC,以便直接公开更多这些环境变量,以避免由此产生的问题。

命令行

要获取在运行Tarpaulin时可用参数的详细帮助,请调用

cargo tarpaulin --help

目前,不需要任何选项,如果没有定义根目录,Tarpaulin将在当前工作目录中运行。

以下是一个使用我们的示例项目之一运行的Tarpaulin示例。这是一个相对简单的测试项目,如果您查看测试,您可以看到测试正确报告了被测试的行。

cargo tarpaulin
Jan 30 21:43:33.715  INFO cargo_tarpaulin::config: Creating config
Jan 30 21:43:33.908  INFO cargo_tarpaulin: Running Tarpaulin
Jan 30 21:43:33.908  INFO cargo_tarpaulin: Building project
Jan 30 21:43:33.908  INFO cargo_tarpaulin::cargo: Cleaning project
   Compiling simple_project v0.1.0 (/home/daniel/personal/tarpaulin/tests/data/simple_project)
    Finished test [unoptimized + debuginfo] target(s) in 0.51s
Jan 30 21:43:34.631  INFO cargo_tarpaulin::process_handling::linux: Launching test
Jan 30 21:43:34.631  INFO cargo_tarpaulin::process_handling: running /home/daniel/personal/tarpaulin/tests/data/simple_project/target/debug/deps/simple_project-417a21905eb8be09

running 1 test
test tests::bad_test ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s

Jan 30 21:43:35.563  INFO cargo_tarpaulin::report: Coverage Results:
|| Uncovered Lines:
|| src/lib.rs: 6
|| src/unused.rs: 4-6
|| Tested/Total Lines:
|| src/lib.rs: 3/4
|| src/unused.rs: 0/3
|| 
42.86% coverage, 3/7 lines covered

Tarpaulin还可以在运行之间报告每个文件的覆盖率变化。如果在上一个示例中更新了测试以覆盖所有行,我们会期望以下输出。

cargo tarpaulin
Jan 30 21:45:37.611  INFO cargo_tarpaulin::config: Creating config
Jan 30 21:45:37.623  INFO cargo_tarpaulin: Running Tarpaulin
Jan 30 21:45:37.623  INFO cargo_tarpaulin: Building project
Jan 30 21:45:37.623  INFO cargo_tarpaulin::cargo: Cleaning project
   Compiling simple_project v0.1.0 (/home/daniel/personal/tarpaulin/tests/data/simple_project)
    Finished test [unoptimized + debuginfo] target(s) in 0.40s
Jan 30 21:45:38.085  INFO cargo_tarpaulin::process_handling::linux: Launching test
Jan 30 21:45:38.085  INFO cargo_tarpaulin::process_handling: running /home/daniel/personal/tarpaulin/tests/data/simple_project/target/debug/deps/simple_project-417a21905eb8be09

running 2 tests
test unused::blah ... ok
test tests::bad_test ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s

Jan 30 21:45:38.990  INFO cargo_tarpaulin::report: Coverage Results:
|| Uncovered Lines:
|| src/lib.rs: 6
|| Tested/Total Lines:
|| src/lib.rs: 3/4 +0.00%
|| src/unused.rs: 3/3 +100.00%
|| 
85.71% coverage, 6/7 lines covered, +42.86% change in coverage

提示:如果您使用coveralls.io与travis-ci一起运行,请使用以下选项--ciserver travis-ci --coveralls $TRAVIS_JOB_ID。coveralls.io的repo-token主要用于私有仓库,它不会为提交的覆盖率结果生成徽章(尽管您仍然可以在coveralls网页界面上查看它们)。有关使用Tarpaulin的项目的示例,您可以查看我的crate keygraph-rs

忽略文件中的代码

Tarpaulin允许您使用属性忽略模块或函数。以下是一个在项目中忽略main函数的示例

#[cfg(not(tarpaulin_include))]
fn main() {
    println!("I won't be included in results");
}

// Also supports the nightly rustc `coverage(off)` attribute.
#[coverage(off)]
fn not_included() {

}

遗憾的是,由于cargo现在发出的意外cfg警告,您可能需要将推荐的lint添加到您的Cargo.toml中,或利用任何现有的构建脚本。如果您使用的是nightly编译器,则使用不稳定的覆盖率属性可能更可取。

然而,skip属性只允许您从覆盖率中排除代码,它不会改变二进制文件中的代码或运行的测试。因此,在为Tarpaulin构建项目时,使用--cfg=tarpaulin允许您有条件地包括/排除编译中的代码。例如,为了使测试在用Tarpaulin构建时不包含在测试二进制文件中且无法运行,请执行以下操作

#[test]
#[cfg(not(tarpaulin))]
fn big_test_not_for_tarpaulin() {
    // Something that would be very slow in tarpaulin or not work
}

如果您仍然希望测试包含在二进制文件中并且默认忽略,请使用

#[test]
#[cfg_attr(tarpaulin, ignore)]
fn ignored_by_tarpaulin() {

}

还有对使用Tarpaulin进行skip的nightly支持的工具属性。例如

#![feature(register_tool)]
#![register_tool(tarpaulin)]

#[tarpaulin::skip]
fn main() {
    println!("I won't be in coverage stats");
}

重新编译

由于Tarpaulin在构建测试时更改了RUSTFLAGS,有时无法避免重新构建测试二进制文件。还有--clean--skip-clean参数,有时默认值已更改,以避免更改RUSTFLAGS时的增量编译问题。如果您旨在减少不必要的重新编译,则尝试添加--skip-clean标志应该是第一步。之后,您可以

  1. 使用cargo tarpaulin --print-rust-flags,并使用这些标志进行开发和覆盖率
  2. 在运行Tarpaulin时使用--target-dir,并进行覆盖率构建和开发构建

持续集成服务

Tarpaulin旨在易于添加到您的CI工作流程中。它对Travis-CI有良好的测试支持,也支持向coveralls.io发送CI特定元数据以供Circle,Semaphore,Jenkins和Codeship使用(尽管只有Jenkins经过测试)。

您还可以在Azure上使用Tarpaulin,查看crate-ci/azure-pipelines以获取示例配置。

Travis-ci和覆盖率网站

最期望的常见用法是通过CI服务启动覆盖率上传到codecov或coveralls这样的网站。鉴于Travis CI内置支持和普及,为新手记录所需的步骤似乎是明智的。要遵循这些步骤,您首先需要一个Travis CI和一个您选择的覆盖率报告网站的配置项目。

我们建议使用最小的rust .travis.yml,安装Tarpaulin依赖的libssl-dev,然后使用您所需的rustc版本运行Tarpaulin。Tarpaulin安装在before_cache中,以便它可以被缓存并防止每次Travis运行时都需要重新安装。您也可以用Tarpaulin的详细运行来替换cargo test,以查看测试结果以及覆盖率输出。

Tarpaulin在成功后运行,因为还有一些不稳定的功能可能会导致覆盖率运行失败。如果您不依赖于这些功能中的任何一个,您可以用cargo tarpaulin来替换cargo test

对于codecov.io,您需要导出CODECOV_TOKEN,在codecov项目的设置中有关于此的说明。

language: rust
# tarpaulin has only been tested on bionic and trusty other distros may have issues
dist: bionic
addons:
    apt:
        packages:
            - libssl-dev
cache: cargo
rust:
  - stable
  - beta
  - nightly
matrix:
  allow_failures:
    - rust: nightly

before_script: |
  if [[ "$TRAVIS_RUST_VERSION" == stable ]]; then
    cargo install cargo-tarpaulin
  fi

script:
- cargo clean
- cargo build
- cargo test

after_success: |
  if [[ "$TRAVIS_RUST_VERSION" == stable ]]; then
    # Uncomment the following line for coveralls.io
    # cargo tarpaulin --ciserver travis-ci --coveralls $TRAVIS_JOB_ID

    # Uncomment the following two lines create and upload a report for codecov.io
    # cargo tarpaulin --out xml
    # bash <(curl -s https://codecov.io/bash)
  fi

如果您依赖于某些nightly功能,您可能需要将before_script更改为before_cache以强制Tarpaulin每次都重新安装。然而,如果可以避免,这将加快您的CI运行速度。

或者,有预构建的Docker镜像或您可以使用cargo-binstall

预构建的二进制文件是使用github actions ubuntu:latest镜像构建的,因此它不适用于xenial或trusty,但在bionic上可以工作。您仍然应该保留推荐的其余Travis设置。

GitHub Actions

文件.github/workflows/coverage.yml示例如何在GitHub Actions中使用dockerseccomp运行覆盖率并将结果推送到

name: coverage

on: [push]
jobs:
  test:
    name: coverage
    runs-on: ubuntu-latest
    container:
      image: xd009642/tarpaulin:develop-nightly
      options: --security-opt seccomp=unconfined
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

      - name: Generate code coverage
        run: |
          cargo +nightly tarpaulin --verbose --all-features --workspace --timeout 120 --out xml

      - name: Upload to codecov.io
        uses: codecov/codecov-action@v2
        with:
          # token: ${{secrets.CODECOV_TOKEN}} # not required for public repos
          fail_ci_if_error: true

CircleCI

要在CircleCI上运行Tarpaulin,您需要在docker中运行Tarpaulin并将机器标志设置为true,如下所示

jobs:
  coverage:
    machine: true
    steps:
      - checkout
      - run:
          name: Coverage with docker
          command: docker run --security-opt seccomp=unconfined -v "${PWD}:/volume" xd009642/tarpaulin

Gitlab Pipelines

要使覆盖率结果显示在Gitlab pipelines中,请在.gitlab-ci.yml中gitlab作业定义的Test coverage部分添加以下正则表达式

job: ...
  coverage: '/^\d+.\d+% coverage/'

Gitlab可以在合并请求的diff中显示覆盖率信息。为此,使用

job: ...
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: cobertura.xml

并生成一个cobertura.xml,如Pycobertura下所述。

对于安装,将cargo install cargo-tarpaulin -f添加到脚本部分。

Docker

Tarpaulin已在docker-hub上部署了构建,要在具有Docker的任何系统上运行Tarpaulin,请在项目目录中运行以下命令

docker run --security-opt seccomp=unconfined -v "${PWD}:/volume" xd009642/tarpaulin

这将使用Docker构建您的项目并运行Tarpaulin,不使用任何参数。还有可用于稳定分支的develop分支上最新版本的标签。版本在0.5.6之后的版本将使用rust stable和nightly编译器构建的最新版本。要获取使用rustc-nightly构建的最新开发版本,请运行以下命令

docker run --security-opt seccomp=unconfined -v "${PWD}:/volume" xd009642/tarpaulin:develop-nightly

请注意,如果 Docker 镜像不包含任何必要的依赖项,构建可能会失败。在这种情况下,您可以在构建之前安装依赖项,如下所示:

docker run --security-opt seccomp=unconfined -v "${PWD}:/volume" xd009642/tarpaulin sh -c "apt-get install xxx && cargo tarpaulin"

或者,将 seccomp json 和将 personality 系统调用的所有 seccomp 操作设置为 SCMP_ACT_ALLOW 以避免删除 Docker 的所有 seccomp 策略。

配置文件

Tarpaulin 允许在 toml 文件中编码多个覆盖率设置。这可以通过参数提供,或者如果项目清单所在的目录或根目录中存在 .tarpaulin.tomltarpaulin.toml,则会使用该配置文件,除非传递了 --ignore-config。下面是一个示例文件

[feature_a_coverage]
features = "feature_a"

[feature_a_and_b_coverage]
features = "feature_a feature_b"
release = true

[report]
coveralls = "coveralls_key"
out = ["Html", "Xml"]

在此,我们将创建三个配置,一个将使用 feature_a 启用运行您的测试,另一个将内置发布和 feature_afeature_b 都启用的测试。最后一个配置使用保留的配置名称 report,这不会导致运行覆盖率,但会影响报告输出。这是一个保留的功能名称,并且任何非报告选项都不会影响 Tarpaulin 的输出。

有关可用键及其类型的参考,请参阅 readme 中的 CLI 帮助文本或 src/config/mod.rs 中的具体类型,如果有什么不清楚的。要在 Tarpaulin 中传递给测试二进制文件的参数中使用 --,请在 toml 文件中使用 args

设置 config 字段不会影响运行,因为它不会解析为额外的配置。

对于 --lib--examples--benches--tests--all-targets--doc--bins 标志,请使用配置文件中的 run-types 条目。

扩展 Tarpaulin

有一些工具可以扩展 Tarpaulin 功能,以满足其他潜在用户需求。

过程宏

通常,Tarpaulin 不能报告过程宏代码中的代码覆盖率。您需要添加一个在运行时展开宏的测试来获取这些统计数据。为此目的创建了一个 runtime-macros crate,其文档描述了如何与 Tarpaulin 一起使用它。

Pycobertura

pycobertura 是一个用于处理 cobertura 报告的 Python 库。它提供报告差异工具以及自己的报告实现。

要生成 cobertura.xml,只需运行以下 Tarpaulin 命令

cargo tarpaulin --out xml

然后使用 pip 安装 pycobertura 并执行所需的命令。

由于 Tarpaulin 允许您更改生成的 cobertura 报告的名称,因此在使用多个提交之间的报告差异时要谨慎。

路线图

  • 测试的分支覆盖率
  • 测试的条件覆盖率
  • MCDC 覆盖率报告
  • LLVM 覆盖率支持
  • 嵌入式目标支持
  • OSX 支持
  • Windows 支持

许可证

Tarpaulin 目前根据 MIT 许可证和 Apache 许可证(版本 2.0)的条款进行许可。有关详细信息,请参阅 LICENSE-MIT 和 LICENSE-APACHE。

依赖项

~14–31MB
~506K SLoC