organizing Rust Project Directory
create a directory structure with the following commands
$ mkdir -p hello/src
mkdir
the command will make a directory the -p
options create a parent directory before creating a child directory
create hello.rs
with the following content :
fn main(){
println!("hello,world!");
}
move hello.rs
source file into hello/src
using the mv
command :
$ mv hello.rs hello/src
use the cd
command to change into that directory and compile your program again :
$ cd hello
$ rustc src/hello.rs
you should now have a hello
executable in the directory.
$ tree
➜ hello tree
.
├── hello
└── src
└── hello.rs
Creating and Running a project with Cargo
start a new rust project is to use the Cargo tool.
➜ hello cd ..
➜ rustlabs rm -rf hello
delete the existing project. the -r
the recursive option will remove the contents of a directory and -f
force option will skip any errors
Then start your project in a new using Cargo like so :
$ cargo new hello
Created binary (application) `hello` package
this should create a new hello
the directory that you can change into
Created binary (application) `hello` package
➜ rustlabs tree
.
└── hello
├── Cargo.toml
└── src
└── main.rs
2 directories, 2 files
➜ rustlabs cd hello
➜ hello git:(master) ✗ tree
.
├── Cargo.toml
└── src
└── main.rs
1 directory, 2 files
Cargo.toml
is a configuration file for the project. the extension .toml
stands for Tom's Obvious, Minimal Language
the src
the directory is for rust source code files
main.rs
is the default starting point for the rust program
hello git:(master) ✗ cat src/main.rs
fn main() {
println!("Hello, world!");
}
we use to compile projects using rustc
to combine the program. to run in one command using cargo run
hello git:(master) ✗ cargo run
Compiling hello v0.1.0 (/Users/sangambiradar/Documents/rustlabs/hello)
Finished dev [unoptimized + debuginfo] target(s) in 0.84s
Running `target/debug/hello`
Hello, world!
if you would like for Cargo to not print status messages about compiling and running the code use the -q
or --quiet
options
cargo run --quiet
Hello, world!
use ls
command to list the content of the current working directory
hello git:(master) ✗ ls
Cargo.lock Cargo.toml src target
you will see the directory target/debug
that contains the build artifacts
➜ hello git:(master) ✗ ./target/debug/hello
Hello, world!
why was the binary file called hello
, though, and not main? to answer that at Cargo.toml
➜ hello git:(master) ✗ ls
Cargo.lock Cargo.toml src target
➜ hello git:(master) ✗ cat Cargo.toml
[package]
name = "hello"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
name = "hello"
of the project created withCargo
so it will also be the name of executableversion = "0.1.0"
is the version of the programedition = "2021"
ate how the rust community introduce changes that are not backward compatible#
comment line[dependencies]
where you will list any external crates your project uses this project has none at this point so it's blank
Rust libraries are called
crates
and they use semantic version numbers in form major.minor.patch so that 1.2.4. a change in a major version indicates breaking changes in the create's public programming interface learn more - https://crates.io
Writing and Running Integration tests
let's create a test directory, goal is to test the hello program by running it on the command line as the user will do . create the file tests/cli.rs
#[test]
fn works(0
assert!(true);
}
#[test]
attribute tells rust to run this function when testingassert!
macro assert that a boolean expression is true
now our project look lake this
➜ hello git:(master) ✗ mkdir tests
➜ hello git:(master) ✗ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/hello`
Hello, world!
➜ hello git:(master) ✗ tree -L 2
.
├── Cargo.lock
├── Cargo.toml
├── src
│ └── main.rs
├── target
│ ├── CACHEDIR.TAG
│ └── debug
└── tests
└── cli.rs
4 directories, 5 files
➜ hello git:(master) ✗
Cargo.lock
file records the exact versions of the dependencies used to build your pogram .[ Note:- you should not edit this file ]the
src
the directory is for the Rust Source code files to build the program.the
target
the directory holds the building artifactsthe
tests
the directory holds the Rust Source code for testing the program
run test
$ cargo run
➜ hello git:(master) ✗ cargo test
Compiling hello v0.1.0 (/Users/sangambiradar/Documents/rustlabs/hello)
Finished test [unoptimized + debuginfo] target(s) in 0.62s
Running unittests src/main.rs (target/debug/deps/hello-2d4f25cc5b31a396)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/cli.rs (target/debug/deps/cli-4a0f9bf0df349d1e)
running 1 test
test works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
the micro assert!
will verify that expectation is true or assert_eq!
to verify that something is an expected value since this test is evaluating the literal value test
, it will always succeed .
#[test]
fn works() {
assert!(false);
}
run cargo test again
hello git:(master) ✗ cargo test
Compiling hello v0.1.0 (/Users/sangambiradar/Documents/rustlabs/hello)
Finished test [unoptimized + debuginfo] target(s) in 0.58s
Running unittests src/main.rs (target/debug/deps/hello-2d4f25cc5b31a396)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/cli.rs (target/debug/deps/cli-4a0f9bf0df349d1e)
running 1 test
test works ... FAILED
failures:
---- works stdout ----
thread 'works' panicked at 'assertion failed: false', tests/cli.rs:3:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
works
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--test cli`
Replace the contents of tests/cli.rs
with the following code :
use std::process::Command;
#[test]
fn runs() {
let mut cmd = Command::new("ls");
let res = cmd.output();
assert!(res.is_ok());
}
Import
std::process::Commnd
. Thestd
tells us this is a standard library and is Rust code that is so universally useful it is included with the languagecreate a new
Command
to runls
Thelet
the keyword will bind a value to a variable andmut
will make variably mutable so that it can change
run test and varify your passong test
cargo test
Compiling hello v0.1.0 (/Users/sangambiradar/Documents/rustlabs/hello)
Finished test [unoptimized + debuginfo] target(s) in 0.53s
Running unittests src/main.rs (target/debug/deps/hello-2d4f25cc5b31a396)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/cli.rs (target/debug/deps/cli-4a0f9bf0df349d1e)
running 1 test
test runs ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
lets update tests/cli.rs
with the following code so that the runs function executes hello
instead of ls
use std::process::Command;
#[test]
fn runs() {
let mut cmd = Command::new("hello");
let res = cmd.output();
assert!(res.is_ok());
}
when you run a test case its get fail because hello
the program can't be found :
hello git:(master) ✗ cargo test
Compiling hello v0.1.0 (/Users/sangambiradar/Documents/rustlabs/hello)
Finished test [unoptimized + debuginfo] target(s) in 0.57s
Running unittests src/main.rs (target/debug/deps/hello-2d4f25cc5b31a396)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/cli.rs (target/debug/deps/cli-4a0f9bf0df349d1e)
running 1 test
test runs ... FAILED
failures:
---- runs stdout ----
thread 'runs' panicked at 'assertion failed: res.is_ok()', tests/cli.rs:6:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
runs
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--test cli`
➜ hello git:(master) ✗
recall the binary
➜ hello git:(master) ✗ hello
zsh: command not found: hello
when you execute any command your operating system look in predefined set of directories for something by neme
hello git:(master) ✗ echo $PATH | tr : '\n'
/Users/sangambiradar/.docker/bin
/Users/sangambiradar/Downloads/google-cloud-sdk/bin
/Library/Frameworks/Python.framework/Versions/3.11/bin
/opt/homebrew/bin
/opt/homebrew/sbin
/usr/local/bin
/System/Cryptexes/App/usr/bin
/usr/bin
/bin
/usr/sbin
/sbin
/Users/sangambiradar/.docker/bin
/Users/sangambiradar/Downloads/google-cloud-sdk/bin
/Library/Frameworks/Python.framework/Versions/3.11/bin
/opt/homebrew/bin
/opt/homebrew/sbin
/Users/sangambiradar/.cargo/bin
➜ hello git:(master) ✗
if we change the directory also it will not work if you refer current directory with binary, not the command
➜ hello git:(master) ✗ cd target/debug
➜ debug git:(master) ✗ hello
zsh: command not found: hello
run executable binary
➜ debug git:(master) ✗ ./hello
Hello, world!
Adding a Project Dependency
we have seen so far only in the target/debug directory. if we can copy it to $PATH directory ) so it will execute and test run successfully . but don't want to copy my program and test it.
I can use crate assert_cmd
to find the program in my crate directory.
[package]
name = "hello"
version = "0.1.0"
edition = "2021"
[dependencies]
[dev-dependencies]
assert_cmd = "1"
using this crate to create a command that looks in Cargo binary directories. that the following test does not verify that the program produces the correct output. update tests/cli.rs
the run function will use assert_cmd::Command
instead of std::process::Command
use assert_cmd::Command;
#[test]
fn runs() {
let mut cmd = Command::cargo_bin("hello").unwrap();
cmd.assert().success();
}
import
assert_cmd::Command
Create
Command
to runhello
in the current crate this returns a Results and code callResult::unwrap
because the binary should be found if not found test will failuse
assert::success
to ensure command successful
Run cargo run
to verify test cases
hello git:(master) ✗ cargo test
Compiling hello v0.1.0 (/Users/sangambiradar/Documents/rustlabs/hello)
Finished test [unoptimized + debuginfo] target(s) in 0.32s
Running unittests src/main.rs (target/debug/deps/hello-73f1e61ebe7b71e0)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/cli.rs (target/debug/deps/cli-734782d1ff788617)
running 1 test
test runs ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s
Understanding Program exit values
what does it mean for a program to run successfully? command-line programs should report the final status to the operating system to indicate success or failure. ( POSIX) standards indicate that the standard exit code is 0 to. indicate success
TRUE(1) General Commands Manual TRUE(1)
NAME
true – return true value
SYNOPSIS
true
DESCRIPTION
The true utility always returns with an exit code of zero.
Some shells may provide a builtin true command which is identical to this utility. Consult the builtin(1) manual page.
SEE ALSO
builtin(1), csh(1), false(1), sh(1)
STANDARDS
The true utility is expected to be IEEE Std 1003.2 (“POSIX.2”) compatible.
macOS 13.1 June 9, 1993 macOS 13.1
if you see the above command notes do nothing except return the exit code zero but I can inspect the bash variable $?
to see the exit status of the recent command
$ true
$ echo $?
0
the false command is will give 1
$ false
$ echo $?
1
let's create src/bin
using mkdir src/bin
then create src/bin/true.rs
with content:
fn main() {
std::process::exit(0)
}
run the program and manually check the exit value
➜ hello git:(master) ✗ cargo run --quiet --bin true
➜ hello git:(master) ✗ echo $?
0
the --bin
the option is the name of the binary target to run
add the following test to test/cli.rs
to ensure it works correctly
cargo test
hello git:(master) ✗ cargo test
Compiling hello v0.1.0 (/Users/sangambiradar/Documents/rustlabs/hello)
Finished test [unoptimized + debuginfo] target(s) in 0.48s
Running unittests src/main.rs (target/debug/deps/hello-73f1e61ebe7b71e0)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/bin/true.rs (target/debug/deps/true-68835b704201d7bf)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/cli.rs (target/debug/deps/cli-e061012399bb0d29)
running 1 test
test true_ok ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s
➜ hello git:(master) ✗
rust programmes will exit with the value zero by default . recall that src/main.rs
doesn't explicitly call std::process::exit
. this means that the true program can do nothing to change src/bin/true.rs
to following
fun main() {}
run the test and verify its still passes
➜ hello git:(master) ✗ cargo run -q --bin true
➜ hello git:(master) ✗ echo $?
0
let's write a false program at the following path src/bin/false.rs
fn main() {
std::process::exit(1);
}
exit with any value between 1 and 255 to indicate an error
✗ cargo run -q --bin false
error[E0601]: `main` function not found in crate `r#false`
|
= note: consider adding a `main` function to `src/bin/false.rs`
For more information about this error, try `rustc --explain E0601`.
error: could not compile `hello` due to previous error
➜ hello git:(master) ✗ echo $?
101
then add this test to tests/cli.ts
to verify that the program reports a failure when run
$ cargo test
another way to write false program use std::process::abort
change src/bin/false
fn main() {
std::process::abort();
}
Testing the program output
The hello world program exits correctly, I'd like to ensure it prints the correct output to STDOUT
which is the standard place for output to appear and it is usually the console. update your runs function in tests/cli.rs
to following :
#[test]
fn runs() {
let mut cmd = Command::cargo_bin("hello").unwrap();
cmd.assert().success().stdout("Hello,World!\n");
}
run the tests and verify the hello does indeed work correctly. change src/main.rs
fn main() {
println!("Hello, world!!!");
}
run the tests again to observe a failing test
hello git:(master) ✗ cargo test
Finished test [unoptimized + debuginfo] target(s) in 0.04s
Running unittests src/bin/false.rs (target/debug/deps/false-8eec864b008dbf20)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/debug/deps/hello-73f1e61ebe7b71e0)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/bin/true.rs (target/debug/deps/true-68835b704201d7bf)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/cli.rs (target/debug/deps/cli-90bd290aca80271c)
running 1 test
test runs ... FAILED
failures:
---- runs stdout ----
thread 'runs' panicked at 'Unexpected stdout, failed diff original var
├── original: Hello,World!
├── diff:
│ --- orig
│ +++ var
│ @@ -1 +1 @@
│ -Hello,World!
│ +Hello, world!!!
└── var as str: Hello, world!!!
command=`"/Users/sangambiradar/Documents/rustlabs/hello/target/debug/hello"`
code=0
stdout=```"Hello, world!!!\n"
stderr=""
', /Users/sangambiradar/.cargo/registry/src/github.com-1ecc6299db9ec823/assert_cmd-1.0.8/src/assert.rs:124:9
note: run with RUST_BACKTRACE=1
environment variable to display a backtrace
failures: runs
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.41s
error: test failed, to rerun pass --test cli
➜ hello git:(master) ✗ cargo test
`-Hello,World!` is the expected output from the program
`+Hello, world!!!` is the output the program actually created
\`command=\`"/Users/sangambiradar/Documents/rustlabs/hello/target/debug/hello"\`\` is shortened version of the command
`code=0` exit code from the program was 0
stdout=\`\`\`"Hello, world!!!\\n"\`\` is the test that was received on `STDOUT`
---
#### exit values make programs composable
the exit value is important because a failed process used in conjunction with another process should cause the combination to failed . for instance, i can use the logical operator && in bash to chain two commands true and ls. Ony if the first process reports success will the second process run
```bash
hello git:(master) ✗ true && ls
Cargo.lock Cargo.toml src target tests