Skip to content

Commit

Permalink
Remove Rust integration testing behaviour from forc test in anticip…
Browse files Browse the repository at this point in the history
…ation of unit testing support (FuelLabs#2264)

This PR is a WIP that aims to address the first 3 steps in FuelLabs#1833.

This is in anticipation of using `forc test` to support unit testing
(rather than Rust integration testing) FuelLabs#1832.

The `forc test` command remains for now, but outputs a message
explaining that the command is now reserved for unit testing and links
to the issues above.

## TODO

- [x] Create a new `sway-test-rs` repo or similar that can be used as a
`cargo generate` template.
- [x] Update Rust integration testing docs in the Sway book to describe
how to use the `cargo generate` command to easily add Sway integration
testing to an existing Rust project.

## Follow-up

- Create a `forc-test-rs` crate that re-exports and extends `fuels` with
useful `forc` functionality for integration testing (e.g. re-exporting
`forc_pkg::build` to ensure sway code is built and available under
`out/` at the start of testing).
  • Loading branch information
mitchmindtree authored Oct 10, 2022
1 parent 71999d3 commit dab1c74
Show file tree
Hide file tree
Showing 19 changed files with 312 additions and 295 deletions.
20 changes: 15 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,15 +158,25 @@ jobs:
run: forc new test-proj
- name: Update project forc manifest to use local sway-lib-std
run: echo "std = { path = \"../sway-lib-std/\" }" >> test-proj/Forc.toml
- name: Build test project
run: forc build --path test-proj
# TODO: Re-add this upon landing unit test support: #1832
# - name: Run test project's test suite
# run: (cd test-proj && forc test)
- name: Install cargo-generate
uses: actions-rs/cargo@v1
with:
command: install
args: cargo-generate
- name: Generate Rust Integration Tests
run: (cd test-proj && cargo generate --init --path ../templates/sway-test-rs --name test-proj)
- name: Update project cargo manifest with workspace
run: |
echo "
[workspace]" >> test-proj/Cargo.toml
- name: Build test project
run: forc build --path test-proj
- name: Run test project's test suite
run: cd test-proj && forc test
- name: Build and Run Default Integration Test
run: (cd test-proj && cargo test)

cargo-build-workspace:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -212,7 +222,7 @@ jobs:
crate: cargo-toml-lint
version: "0.1"
- name: Run Cargo.toml linter
run: git ls-files | grep Cargo.toml$ | xargs --verbose -n 1 cargo-toml-lint
run: git ls-files | grep Cargo.toml$ | grep -v 'templates/sway-test-rs' | xargs --verbose -n 1 cargo-toml-lint

cargo-fmt-check:
runs-on: ubuntu-latest
Expand Down
19 changes: 2 additions & 17 deletions docs/src/introduction/forc_project.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,9 @@ Here is the project that Forc has initialized:
```console
$ cd my-fuel-project
$ tree .
├── Cargo.toml
├── Forc.toml
├── src
│   └── main.sw
└── tests
└── harness.rs
└── src
   └── main.sw
```

`Forc.toml` is the _manifest file_ (similar to `Cargo.toml` for Cargo or `package.json` for Node), and defines project metadata such as the project name and dependencies.
Expand Down Expand Up @@ -76,15 +73,3 @@ data_0 .word 559005003
Compiled contract "my-fuel-project".
Bytecode size is 60 bytes.
```

To test this contract, use `forc test`:

```console
$ forc test
running 1 test
test can_get_contract_id ... ok

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

The `forc test` command tests the contract using the Rust SDK test harness that lives under `tests/`. The default test harness `harness.rs` contains boilerplate code to get you started but doesn't actually call any contract methods. For additional information on testing contracts using the Rust SDK, refer to the [Testing with Rust](../testing/testing-with-rust.md) section.
11 changes: 10 additions & 1 deletion docs/src/testing/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Testing

Testing your Sway contracts can be done with the Rust SDK.
Sway aims to provide facilities for both unit testing and integration testing.

**Unit testing** refers to "in-language" testing which can be triggered via the
`forc test` command. Sway unit testing is currently a high-priority
work-in-progress, you can follow along at [this
issue](https://github.com/FuelLabs/sway/issues/1832).

**Integration testing** refers to the testing of your Sway project's integration
within some wider application. You can add integration testing to your Sway+Rust
projects today using the cargo generate template and Rust SDK.

- [Testing with Rust](./testing-with-rust.md)
181 changes: 167 additions & 14 deletions docs/src/testing/testing-with-rust.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,169 @@
# Testing with Rust

If you look again at the project structure when you create a [new Forc project](../introduction/forc_project.md) with `forc new`, you can see a directory called `tests/`:
A common use of Sway is for writing contracts or scripts that exist as part of a
wider Rust application. In order to test the interaction between our Sway code
and our Rust code we can add integration testing.

```plaintext
$ forc new my-fuel-project
## Adding Rust Integration Testing

To add Rust integration testing to a Forc project we can use [the `sway-test-rs`
cargo generate
template](https://github.com/FuelLabs/sway/tree/master/templates/sway-test-rs).
This template makes it easy for Sway devs to add the boilerplate required when
setting up their Rust integration testing.

Let's add a Rust integration test to [the fresh project we created in the
introduction](../introduction/forc_project.md).

### 1. Enter the project

To recap, here's what our empty project looks like:

```console
$ cd my-fuel-project
$ tree .
├── Forc.toml
└── src
   └── main.sw
```

### 2. Install `cargo generate`

We're going to add a Rust integration test harness using a cargo generate
template. Let's make sure we have the `cargo generate` command installed!

```console
cargo install cargo-generate
```

> _**Note**: You can learn more about cargo generate by visiting [its
> repository](https://github.com/cargo-generate/cargo-generate)._
### 3. Generate the test harness

Let's generate the default test harness with the following:

```console
cargo generate --init fuellabs/sway templates/sway-test-rs --name my-fuel-project
```

If all goes well, the output should look as follows:

```console
⚠️ Favorite `fuellabs/sway` not found in config, using it as a git repository: https://github.com/fuellabs/sway
🤷 Project Name : my-fuel-project
🔧 Destination: /home/user/path/to/my-fuel-project ...
🔧 Generating template ...
[1/3] Done: Cargo.toml
[2/3] Done: tests/harness.rs
[3/3] Done: tests
🔧 Moving generated files into: `/home/user/path/to/my-fuel-project`...
✨ Done! New project created /home/user/path/to/my-fuel-project
```

Let's have a look at the result:

```console
$ tree .
├── Cargo.toml
├── Forc.toml
├── src
│   └── main.sw
└── tests
└── harness.rs
```

We have two new files!

- The `Cargo.toml` is the manifest for our new test harness and specifies the
required dependencies including `fuels` the Fuel Rust SDK.
- The `tests/harness.rs` contains some boilerplate test code to get us started,
though doesn't call any contract methods just yet.

### 4. Build the forc project

Before running the tests, we need to build our contract so that the necessary
ABI, storage and bytecode artifacts are available. We can do so with `forc
build`:

```console
$ forc build
Creating a new `Forc.lock` file. (Cause: lock file did not exist)
Adding core
Adding std git+https://github.com/fuellabs/sway?tag=v0.24.5#e695606d8884a18664f6231681333a784e623bc9
Created new lock file at /home/user/path/to/my-fuel-project/Forc.lock
Compiled library "core".
Compiled library "std".
Compiled contract "my-fuel-project".
Bytecode size is 60 bytes.
```

At this point, our project should look like the following:

```console
$ tree
├── Cargo.toml
├── Forc.lock
├── Forc.toml
├── out
│   └── debug
│   ├── my-fuel-project-abi.json
│   ├── my-fuel-project.bin
│   └── my-fuel-project-storage_slots.json
├── src
│   └── main.sw
└── tests
└── harness.rs
```

Note that this is also a Rust package, hence the existence of a `Cargo.toml` (Rust manifest file) in the project root directory. The `Cargo.toml` in the root directory contains necessary Rust dependencies to enable you to write Rust-based tests using our [Rust SDK](https://fuellabs.github.io/fuels-rs/latest), (`fuels-rs`).
We now have an `out` directory with our required JSON files!

> _**Note**: This step may no longer be required in the future as we plan to
> enable the integration testing to automatically build the artifacts as
> necessary so that files like the ABI JSON are always up to date._
### 5. Build and run the tests

Now we're ready to build and run the default integration test.

```console
$ cargo test
Updating crates.io index
Compiling version_check v0.9.4
Compiling proc-macro2 v1.0.46
Compiling quote v1.0.21
...
Compiling fuels v0.24.0
Compiling my-fuel-project v0.1.0 (/home/user/path/to/my-fuel-project)
Finished test [unoptimized + debuginfo] target(s) in 1m 03s
Running tests/harness.rs (target/debug/deps/integration_tests-373971ac377845f7)

running 1 test
test can_get_contract_id ... ok

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

> _**Note**: The first time we run `cargo test`, cargo will spend some time
> fetching and building the dependencies for Fuel's Rust SDK. This might take a
> while, but only the first time!_
If all went well, we should see some output that looks like the above!

These tests can be run using `forc test` which will look for Rust tests under the `tests/` directory (created automatically with `forc new` and prepopulated with boilerplate code).
## Writing Tests

For example, let's write tests against the following contract, written in Sway. This can be done in the pregenerated `src/main.sw` or in a new file in `src`. In the case of the latter, update the `entry` field in `Forc.toml` to point at the new contract.
Now that we've learned how to setup Rust integration testing in our project,
let's try to write some of our own tests!

First, let's update our contract code with a simple counter example:

```sway
{{#include ../../../examples/counter/src/main.sw}}
```

Our `tests/harness.rs` file could look like:
To test our `initialize_counter` and `increment_counter` contract methods from
the Rust test harness, we could update our `tests/harness.rs` file with the
following:

<!--TODO add test here once examples are tested-->

Expand Down Expand Up @@ -58,13 +197,13 @@ async fn get_contract_instance() -> (TestContract, ContractId) {
.await
.unwrap();
let instance = TestContractBuilder::new(id.to_string(), wallet).build();
let instance = TestContract::new(id.to_string(), wallet);
(instance, id.into())
}
#[tokio::test]
async fn can_get_contract_id() {
async fn initialize_and_increment() {
let (contract_instance, _id) = get_contract_instance().await;
// Now you have an instance of your contract you can use to test each function
Expand All @@ -87,13 +226,27 @@ async fn can_get_contract_id() {
}
```

Then, in the root of our project, running `forc test` will run the test above, compiling and deploying the contract to a local Fuel network, and calling the ABI methods against the contract deployed in there:
Let's build our project once more and run the test:

```console
forc build
```

```console
$ forc test
...
$ cargo test
Compiling my-fuel-project v0.1.0 (/home/mindtree/programming/sway/my-fuel-project)
Finished test [unoptimized + debuginfo] target(s) in 11.61s
Running tests/harness.rs (target/debug/deps/integration_tests-373971ac377845f7)

running 1 test
test can_get_contract_id ... ok
test initialize_and_increment ... ok

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

When cargo runs our test, our test uses the SDK to spin up a local in-memory
Fuel network, deploy our contract to it, and call the contract methods via the
ABI.

You can add as many functions decorated with `#[tokio::test]` as you like, and
`cargo test` will automatically test each of them!
4 changes: 0 additions & 4 deletions forc-util/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,6 @@ pub fn find_parent_dir_with_file(starter_path: &Path, file_name: &str) -> Option
pub fn find_manifest_dir(starter_path: &Path) -> Option<PathBuf> {
find_parent_dir_with_file(starter_path, constants::MANIFEST_FILE_NAME)
}
/// Continually go up in the file tree until a Cargo manifest file is found.
pub fn find_cargo_manifest_dir(starter_path: &Path) -> Option<PathBuf> {
find_parent_dir_with_file(starter_path, constants::TEST_MANIFEST_FILE_NAME)
}

pub fn is_sway_file(file: &Path) -> bool {
let res = file.extension();
Expand Down
4 changes: 1 addition & 3 deletions forc/src/cli/commands/clean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ use crate::ops::forc_clean;
use anyhow::Result;
use clap::Parser;

/// Removes the default forc compiler output artifact directory, i.e. `<project-name>/out`. Also
/// calls `cargo clean` which removes the `target` directory generated by `cargo` when running
/// tests.
/// Removes the default forc compiler output artifact directory, i.e. `<project-name>/out`.
#[derive(Debug, Parser)]
pub struct Command {
/// Path to the project, if not specified, current working directory will be used.
Expand Down
Loading

0 comments on commit dab1c74

Please sign in to comment.