mirror of
https://github.com/darkicewolf50/RustBrock.git
synced 2025-07-06 11:07:12 -06:00
This commit is contained in:
37
.gitea/workflows/testing.yaml
Normal file
37
.gitea/workflows/testing.yaml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# name of the workflow.
|
||||||
|
# this is optional.
|
||||||
|
name: Test Gitea Actions
|
||||||
|
|
||||||
|
# events that will trigger this workflow.
|
||||||
|
# here, we only have "pull_request", so the workflow will run
|
||||||
|
# whenever we create a pull request.
|
||||||
|
# other examples: [push] and [pull_request, push]
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
|
||||||
|
# each workflow must have at least one job.
|
||||||
|
# jobs run in parallel by default (we can change that).
|
||||||
|
# each job groups together a series of steps to accomplish a purpose.
|
||||||
|
jobs:
|
||||||
|
# name of the job
|
||||||
|
first:
|
||||||
|
# the platform or OS that the workflow will run on.
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
# series of steps to finish the job.
|
||||||
|
steps:
|
||||||
|
# name of the step.
|
||||||
|
# steps run sequentially.
|
||||||
|
# this is optionale
|
||||||
|
- name: checkout
|
||||||
|
# each step can either have "uses" or "run".
|
||||||
|
# "uses" run an action written somewhere other than this workflow .
|
||||||
|
# usually from the community.
|
||||||
|
# this action checks out the repo code to the runner (instance)
|
||||||
|
# running the action
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
# another step.
|
||||||
|
# this step runs a bash (Ubuntu's default shell) command
|
||||||
|
- name: list files
|
||||||
|
run: ls
|
23
.obsidian/workspace.json
vendored
23
.obsidian/workspace.json
vendored
@ -24,6 +24,20 @@
|
|||||||
{
|
{
|
||||||
"id": "53b36d00b704136e",
|
"id": "53b36d00b704136e",
|
||||||
"type": "leaf",
|
"type": "leaf",
|
||||||
|
"state": {
|
||||||
|
"type": "markdown",
|
||||||
|
"state": {
|
||||||
|
"file": "Tests.md",
|
||||||
|
"mode": "source",
|
||||||
|
"source": false
|
||||||
|
},
|
||||||
|
"icon": "lucide-file",
|
||||||
|
"title": "Tests"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ec34d2df2728a299",
|
||||||
|
"type": "leaf",
|
||||||
"state": {
|
"state": {
|
||||||
"type": "markdown",
|
"type": "markdown",
|
||||||
"state": {
|
"state": {
|
||||||
@ -36,7 +50,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"currentTab": 1
|
"currentTab": 2
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"direction": "vertical"
|
"direction": "vertical"
|
||||||
@ -179,13 +193,14 @@
|
|||||||
"command-palette:Open command palette": false
|
"command-palette:Open command palette": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"active": "53b36d00b704136e",
|
"active": "ec34d2df2728a299",
|
||||||
"lastOpenFiles": [
|
"lastOpenFiles": [
|
||||||
|
"Writing_Tests.md",
|
||||||
|
"Tests.md",
|
||||||
|
"minigrep/README.md",
|
||||||
"minigrep/src/lib.rs",
|
"minigrep/src/lib.rs",
|
||||||
"Test_Organization.md",
|
"Test_Organization.md",
|
||||||
"Test Controls.md",
|
"Test Controls.md",
|
||||||
"Tests.md",
|
|
||||||
"Writing_Tests.md",
|
|
||||||
"Traits.md",
|
"Traits.md",
|
||||||
"Modules and Use.md",
|
"Modules and Use.md",
|
||||||
"Modules.md",
|
"Modules.md",
|
||||||
|
@ -727,4 +727,301 @@ We add `use minigrep::Config` line to bring the `Coinfig` type from the library
|
|||||||
|
|
||||||
And we prefix the `run` function with our crate name so that it can also be used
|
And we prefix the `run` function with our crate name so that it can also be used
|
||||||
|
|
||||||
This work sets up for success in the future.
|
This work sets up for success in the future.
|
||||||
|
|
||||||
|
### Sixth Goal: Developing the Library's Functionality with Test-Driven Development
|
||||||
|
Now that the code and logic has been extracted out of *main.rs* and left behind the argument collecting and error handling
|
||||||
|
|
||||||
|
It is now much easier and possible to write tests for the core functionality of the code.
|
||||||
|
|
||||||
|
We can now call functions directly with various arguments and check the return values without having to call our binary from the command line.
|
||||||
|
|
||||||
|
This goal's section will focus on adding the search logic to the `minigrep` program using the test-driven development (TDD) process with the steps:
|
||||||
|
1. Write a test that fails and run it to make sure it fails for the reason you expect
|
||||||
|
2. Write or modify just enough code to make the new test pass
|
||||||
|
3. Refactor the code you just added or changed and make sure the tests continue to pass
|
||||||
|
4. Repeat form step 1
|
||||||
|
|
||||||
|
Even though this is one of many was to write software, TDD can help drive code design
|
||||||
|
|
||||||
|
Writing the tests before you write code that makes the test pass helps to maintain high test coverage throughout the process.
|
||||||
|
|
||||||
|
We will test drive the implementation of the functionality that will actually do the searching for the query string in the file contents and produce a list of lines that match the query
|
||||||
|
|
||||||
|
We will add this in the function called `search`
|
||||||
|
|
||||||
|
#### Writing a Failing Test
|
||||||
|
First lets remove the `println!` statements because we don't need them anymore to check the program's behavior.
|
||||||
|
|
||||||
|
Next we'll add a `tests` module with a test function the same as [The Test Anatomy](../Writing_Tests.md) from before.
|
||||||
|
|
||||||
|
This test will specify the behavior we want the `search` function to have
|
||||||
|
- It will take a query and the test to search
|
||||||
|
- it will return only the lines form the text that contain the query
|
||||||
|
Here is the test (it goes in *src/lib.rs*)
|
||||||
|
Note it will not compile yet
|
||||||
|
```rust
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_result() {
|
||||||
|
let query = "duct";
|
||||||
|
let contents = "\
|
||||||
|
Rust:
|
||||||
|
safe, fast, productive.
|
||||||
|
Pick three.";
|
||||||
|
|
||||||
|
assert_eq!(vec!["safe, fast, productive."], search(query, contents));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
This test will search for the string `"duct"`
|
||||||
|
|
||||||
|
The test we will search three lines only one that contains `"duct"`
|
||||||
|
|
||||||
|
Note that the backslash after the opening double quote tells Rust not to put a newline character at the beginning of the contents of this string literal.
|
||||||
|
|
||||||
|
We will then assert that the value returned from the `search` function only contains the line we expect
|
||||||
|
|
||||||
|
We aren't yet able o run this test and watch it fail because the function it needs in order to compile and run doesn't exist yet.
|
||||||
|
|
||||||
|
In accordance with TDD principles we will add just enough code to compile and run by adding a definition of the `search` function that always returns an empty vector that doesn't match with the one in the assert.
|
||||||
|
|
||||||
|
Here the what the function will look like at this point
|
||||||
|
```rust
|
||||||
|
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice that we need to define an explicit lifetime `'a` in the signature of `search` and use that lifetime with the `contents` argument and the return value.
|
||||||
|
|
||||||
|
This case specifies that the vector returned should contain string slices that reference slices of the argument `contents` (rather than the argument `query`).
|
||||||
|
|
||||||
|
It also could be said that the returned value will live as long as what was passed into the `contents` arguments.
|
||||||
|
|
||||||
|
This is important the data referenced *by* a slice needs to be valid for the reference to be valid.
|
||||||
|
|
||||||
|
If the compiler assumes we are making string slices of `query` rather than `contents` it will do its safety checking incorrectly.
|
||||||
|
|
||||||
|
|
||||||
|
If we forget lifetime annotations and try to compile we will get this error
|
||||||
|
```
|
||||||
|
$ cargo build
|
||||||
|
Compiling minigrep v0.1.0 (file:///projects/minigrep)
|
||||||
|
error[E0106]: missing lifetime specifier
|
||||||
|
--> src/lib.rs:28:51
|
||||||
|
|
|
||||||
|
28 | pub fn search(query: &str, contents: &str) -> Vec<&str> {
|
||||||
|
| ---- ---- ^ expected named lifetime parameter
|
||||||
|
|
|
||||||
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `query` or `contents`
|
||||||
|
help: consider introducing a named lifetime parameter
|
||||||
|
|
|
||||||
|
28 | pub fn search<'a>(query: &'a str, contents: &'a str) -> Vec<&'a str> {
|
||||||
|
| ++++ ++ ++ ++
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0106`.
|
||||||
|
error: could not compile `minigrep` (lib) due to 1 previous error
|
||||||
|
```
|
||||||
|
|
||||||
|
Rust can't possibly know which of the two args we need so we need to tell it explicitly.
|
||||||
|
|
||||||
|
Due to `contents` is the arguments that contains all of our text we want to return the parts of that text that match.
|
||||||
|
|
||||||
|
This shows that `contents` is the argument that should be connected to the return value using the lifetime syntax.
|
||||||
|
|
||||||
|
Other programming languages don't require you to connect the arguments to return value, but this practice will get easier over time with more exposure.
|
||||||
|
|
||||||
|
Here is the output of the test
|
||||||
|
```
|
||||||
|
$ cargo test
|
||||||
|
Compiling minigrep v0.1.0 (file:///projects/minigrep)
|
||||||
|
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.97s
|
||||||
|
Running unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)
|
||||||
|
|
||||||
|
running 1 test
|
||||||
|
test tests::one_result ... FAILED
|
||||||
|
|
||||||
|
failures:
|
||||||
|
|
||||||
|
---- tests::one_result stdout ----
|
||||||
|
thread 'tests::one_result' panicked at src/lib.rs:44:9:
|
||||||
|
assertion `left == right` failed
|
||||||
|
left: ["safe, fast, productive."]
|
||||||
|
right: []
|
||||||
|
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
||||||
|
|
||||||
|
|
||||||
|
failures:
|
||||||
|
tests::one_result
|
||||||
|
|
||||||
|
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
|
||||||
|
error: test failed, to rerun pass `--lib`
|
||||||
|
```
|
||||||
|
This test fails exactly as expected
|
||||||
|
|
||||||
|
#### Writing Code to Pass the Test
|
||||||
|
Our test is failing because we always return an empty vector
|
||||||
|
|
||||||
|
To fix this and implement `search`, the program needs to follow these steps:
|
||||||
|
1. Iterate through each line of the contents
|
||||||
|
2. Check whether the line contains the query string
|
||||||
|
3. If it does add it to the list of values we are returning
|
||||||
|
4. If it doesn't do nothing
|
||||||
|
5. Return the list of result that match
|
||||||
|
|
||||||
|
##### Iterating Through Lines with the lines Method
|
||||||
|
Rust includes a helpful method to handle line-by-line iterations of strings, named `lines`
|
||||||
|
|
||||||
|
Here it is how it would be used in this case
|
||||||
|
```rust
|
||||||
|
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||||
|
for line in contents.lines() {
|
||||||
|
// do something with line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
The `lines` method returns an iterator.
|
||||||
|
For now recall that when used in a `for` loop with an iterator to run some code on each item in a collection
|
||||||
|
|
||||||
|
##### Searching each Line for the Query
|
||||||
|
Next we will check whether the current line contains our query string.
|
||||||
|
|
||||||
|
Strings have a helpful method named `contains` that does this for us.
|
||||||
|
|
||||||
|
now lets add a call to the `contains` method in the `search` function
|
||||||
|
|
||||||
|
Here is the updated function
|
||||||
|
|
||||||
|
Note it still will not compile
|
||||||
|
```rust
|
||||||
|
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||||
|
for line in contents.lines() {
|
||||||
|
if line.contains(query) {
|
||||||
|
// do something with line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
At the moment we are only building up functionality
|
||||||
|
|
||||||
|
To get the code to compile we need to return a value from the body as we indicated in the function signature
|
||||||
|
|
||||||
|
##### Storing Matching Lines
|
||||||
|
To finish this function we need a way to store the matching lines that we want to return.
|
||||||
|
|
||||||
|
To do this for now we can make a mutable vector before the `for` loop and call the `push` method to store a `line` in the vector
|
||||||
|
|
||||||
|
After the `for` loop the vector will be returned
|
||||||
|
|
||||||
|
Here is what the function like after adding the `vector` and the `push` method
|
||||||
|
|
||||||
|
Note it will now compile
|
||||||
|
```rust
|
||||||
|
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||||
|
let mut results = Vec::new();
|
||||||
|
|
||||||
|
for line in contents.lines() {
|
||||||
|
if line.contains(query) {
|
||||||
|
results.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Now the `search` function should return only the lines that contain `query` and the test should pass
|
||||||
|
|
||||||
|
Here is the output when running the test at this point
|
||||||
|
```
|
||||||
|
$ cargo test
|
||||||
|
Compiling minigrep v0.1.0 (file:///projects/minigrep)
|
||||||
|
Finished `test` profile [unoptimized + debuginfo] target(s) in 1.22s
|
||||||
|
Running unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)
|
||||||
|
|
||||||
|
running 1 test
|
||||||
|
test tests::one_result ... ok
|
||||||
|
|
||||||
|
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
|
||||||
|
Running unittests src/main.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
|
||||||
|
Doc-tests minigrep
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
```
|
||||||
|
|
||||||
|
As we can now see the test passes.
|
||||||
|
|
||||||
|
At this point we could consider opportunities for refactoring the implementation of the search function while keeping the tests passing to maintain the same functionality
|
||||||
|
|
||||||
|
The code in the search function isn't too bad but it doesn't take advantage of some useful features that iterators have
|
||||||
|
|
||||||
|
This will be further improved in the iterators chapter
|
||||||
|
|
||||||
|
##### Using the Search Function in the `run` Function
|
||||||
|
Now that the `search` function is working and tested, we now need to call `search` from our `run` function.
|
||||||
|
|
||||||
|
We need to pass the `config.query` value and the `contents` that `run` reads from the file to search function.
|
||||||
|
|
||||||
|
Then `run` will print each line returned from `search`
|
||||||
|
|
||||||
|
Here is what run will look like now
|
||||||
|
```rust
|
||||||
|
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
|
||||||
|
let contents = fs::read_to_string(config.file_path)?;
|
||||||
|
|
||||||
|
for line in search(&config.query, &contents) {
|
||||||
|
println!("{line}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We are still using a `for` loop to return each line form `search` and print it
|
||||||
|
|
||||||
|
Now that the entire program should work
|
||||||
|
|
||||||
|
Lets try it with first with a word that should return exactly one line from the Emily Dickinson poem: *frog*
|
||||||
|
|
||||||
|
Here is the output
|
||||||
|
```
|
||||||
|
$ cargo run -- frog poem.txt
|
||||||
|
Compiling minigrep v0.1.0 (file:///projects/minigrep)
|
||||||
|
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.38s
|
||||||
|
Running `target/debug/minigrep frog poem.txt`
|
||||||
|
How public, like a frog
|
||||||
|
```
|
||||||
|
|
||||||
|
Now lets try with a word that will match multiple lines like *body*
|
||||||
|
```
|
||||||
|
$ cargo run -- body poem.txt
|
||||||
|
Compiling minigrep v0.1.0 (file:///projects/minigrep)
|
||||||
|
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
|
||||||
|
Running `target/debug/minigrep body poem.txt`
|
||||||
|
I'm nobody! Who are you?
|
||||||
|
Are you nobody, too?
|
||||||
|
How dreary to be somebody!
|
||||||
|
```
|
||||||
|
|
||||||
|
Then lets make sure we don' get any lines when we search for a word that isn't anywhere such as *monomorphization*
|
||||||
|
```
|
||||||
|
$ cargo run -- monomorphization poem.txt
|
||||||
|
Compiling minigrep v0.1.0 (file:///projects/minigrep)
|
||||||
|
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
|
||||||
|
Running `target/debug/minigrep monomorphization poem.txt`
|
||||||
|
```
|
||||||
|
|
||||||
|
Now that it is finished we will finished off with a demonstration on how to work with environment variables and how to print a std error, both are useful when you are writing command line programs
|
||||||
|
|
||||||
|
@ -23,7 +23,39 @@ impl Config {
|
|||||||
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
|
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
|
||||||
let contents = fs::read_to_string(config.file_path)?;
|
let contents = fs::read_to_string(config.file_path)?;
|
||||||
|
|
||||||
println!("With text:\n{contents}")
|
// refactor 10
|
||||||
|
// println!("With text:\n{contents}")
|
||||||
|
|
||||||
|
for line in search(&config.query, &contents) {
|
||||||
|
println!("{line}");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||||
|
// original that only can fail
|
||||||
|
// vec![]
|
||||||
|
|
||||||
|
for line in contents.lines() {
|
||||||
|
if line.contains(query) {
|
||||||
|
// do something with line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_result() {
|
||||||
|
let query = "duct";
|
||||||
|
let contents = "\
|
||||||
|
Rust:
|
||||||
|
safe, fast, productive.
|
||||||
|
Pick three.";
|
||||||
|
|
||||||
|
assert_eq!(vec!["safe, fast, productive."], search(query, contents));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -30,12 +30,13 @@ fn main() {
|
|||||||
// process::exit(1);
|
// process::exit(1);
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
// refactor 10
|
||||||
println!("Searching for {}", config.query);
|
// println!("Searching for {}", config.query);
|
||||||
println!("In the file {}", config.file_path);
|
// println!("In the file {}", config.file_path);
|
||||||
|
|
||||||
// refactor 8
|
// refactor 8
|
||||||
if let Err(e) = minigrep::run(config) {
|
if let Err(e) = minigrep::run(config) {
|
||||||
|
// needed for helping the user
|
||||||
println!("Application error: {e}");
|
println!("Application error: {e}");
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user