1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-06-16 14:17:34 +02:00

Comprehensive Rust v2 (#1073)

I've taken some work by @fw-immunant and others on the new organization
of the course and condensed it into a form amenable to a text editor and
some computational analysis. You can see the inputs in `course.py` but
the interesting bits are the output: `outline.md` and `slides.md`.

The idea is to break the course into more, smaller segments with
exercises at the ends and breaks in between. So `outline.md` lists the
segments, their duration, and sums those durations up per-day. It shows
we're about an hour too long right now! There are more details of the
segments in `slides.md`, or you can see mostly the same stuff in
`course.py`.

This now contains all of the content from the v1 course, ensuring both
that we've covered everything and that we'll have somewhere to redirect
every page.

Fixes #1082.
Fixes #1465.

---------

Co-authored-by: Nicole LeGare <dlegare.1001@gmail.com>
Co-authored-by: Martin Geisler <mgeisler@google.com>
This commit is contained in:
Dustin J. Mitchell
2023-11-29 10:39:24 -05:00
committed by GitHub
parent ea204774b6
commit 6d19292f16
309 changed files with 6807 additions and 4281 deletions

10
src/testing/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "testing"
version = "0.1.0"
edition = "2021"
publish = false
[[bin]]
name = "luhn"
path = "exercise.rs"

37
src/testing/exercise.md Normal file
View File

@ -0,0 +1,37 @@
---
minutes: 30
---
# Exercise: Luhn Algorithm
# Luhn Algorithm
The [Luhn algorithm](https://en.wikipedia.org/wiki/Luhn_algorithm) is used to
validate credit card numbers. The algorithm takes a string as input and does the
following to validate the credit card number:
* Ignore all spaces. Reject number with less than two digits.
* Moving from **right to left**, double every second digit: for the number `1234`,
we double `3` and `1`. For the number `98765`, we double `6` and `8`.
* After doubling a digit, sum the digits if the result is greater than 9. So doubling `7` becomes `14` which
becomes `1 + 4 = 5`.
* Sum all the undoubled and doubled digits.
* The credit card number is valid if the sum ends with `0`.
The provided code provides a buggy implementation of the luhn algorithm, along
with two basic unit tests that confirm that most the algorithm is implemented
correctly.
Copy the code below to <https://play.rust-lang.org/> and write additional tests
to uncover bugs in the provided implementation, fixing any bugs you find.
```rust
{{#include exercise.rs:luhn}}
{{#include exercise.rs:unit-tests}}
}
```

126
src/testing/exercise.rs Normal file
View File

@ -0,0 +1,126 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ANCHOR: solution
// This is the buggy version that appears in the problem.
#[cfg(never)]
// ANCHOR: luhn
pub fn luhn(cc_number: &str) -> bool {
let mut sum = 0;
let mut double = false;
for c in cc_number.chars().rev() {
if let Some(digit) = c.to_digit(10) {
if double {
let double_digit = digit * 2;
sum += if double_digit > 9 {
double_digit - 9
} else {
double_digit
};
} else {
sum += digit;
}
double = !double;
} else {
continue;
}
}
sum % 10 == 0
}
// ANCHOR_END: luhn
// This is the solution and passes all of the tests below.
pub fn luhn(cc_number: &str) -> bool {
let mut sum = 0;
let mut double = false;
let mut digits = 0;
for c in cc_number.chars().rev() {
if let Some(digit) = c.to_digit(10) {
digits += 1;
if double {
let double_digit = digit * 2;
sum += if double_digit > 9 {
double_digit - 9
} else {
double_digit
};
} else {
sum += digit;
}
double = !double;
} else if c.is_whitespace() {
continue;
} else {
return false;
}
}
digits >= 2 && sum % 10 == 0
}
fn main() {
let cc_number = "1234 5678 1234 5670";
println!(
"Is {cc_number} a valid credit card number? {}",
if luhn(cc_number) { "yes" } else { "no" }
);
}
// ANCHOR: unit-tests
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_valid_cc_number() {
assert!(luhn("4263 9826 4026 9299"));
assert!(luhn("4539 3195 0343 6467"));
assert!(luhn("7992 7398 713"));
}
#[test]
fn test_invalid_cc_number() {
assert!(!luhn("4223 9826 4026 9299"));
assert!(!luhn("4539 3195 0343 6476"));
assert!(!luhn("8273 1232 7352 0569"));
}
// ANCHOR_END: unit-tests
#[test]
fn test_non_digit_cc_number() {
assert!(!luhn("foo"));
assert!(!luhn("foo 0 0"));
}
#[test]
fn test_empty_cc_number() {
assert!(!luhn(""));
assert!(!luhn(" "));
assert!(!luhn(" "));
assert!(!luhn(" "));
}
#[test]
fn test_single_digit_cc_number() {
assert!(!luhn("0"));
}
#[test]
fn test_two_digit_cc_number() {
assert!(luhn(" 0 0 "));
}
}

View File

@ -1,16 +0,0 @@
# Integration Tests
If you want to test your library as a client, use an integration test.
Create a `.rs` file under `tests/`:
```rust,ignore
use my_library::init;
#[test]
fn test_init() {
assert!(init().is_ok());
}
```
These tests only have access to the public API of your crate.

35
src/testing/lints.md Normal file
View File

@ -0,0 +1,35 @@
---
minutes: 5
---
# Compiler Lints and Clippy
The Rust compiler produces fantastic error messages, as well as helpful
built-in lints. [Clippy](https://doc.rust-lang.org/clippy/) provides even more
lints, organized into groups that can be enabled per-project.
```rust,editable,should_panic
#[deny(clippy::cast_possible_truncation)]
fn main() {
let x = 3;
while (x < 70000) {
x *= 2;
}
println!("X probably fits in a u16, right? {}", x as u16);
}
```
<details>
Run the code sample and examine the error message. There are also lints visible
here, but those will not be shown once the code compiles. Switch to the
Playground site to show those lints.
After resolving the lints, run `clippy` on the playground site to show clippy
warnings. Clippy has extensive documentation of its lints, and adds new lints
(including default-deny lints) all the time.
Note that errors or warnings with `help: ...` can be fixed with `cargo fix` or
via your editor.
</details>

View File

@ -1,4 +1,28 @@
# Documentation Tests
---
minutes: 10
---
# Other Types of Tests
## Integration Tests
If you want to test your library as a client, use an integration test.
Create a `.rs` file under `tests/`:
```rust,ignore
// tests/my_library.rs
use my_library::init;
#[test]
fn test_init() {
assert!(init().is_ok());
}
```
These tests only have access to the public API of your crate.
## Documentation Tests
Rust has built-in support for documentation tests:

5
src/testing/solution.md Normal file
View File

@ -0,0 +1,5 @@
# Solution
```rust,editable
{{#include exercise.rs:solution}}
```

View File

@ -1,27 +0,0 @@
# Test Modules
Unit tests are often put in a nested module (run tests on the
[Playground](https://play.rust-lang.org/)):
```rust,editable
fn helper(a: &str, b: &str) -> String {
format!("{a} {b}")
}
pub fn main() {
println!("{}", helper("Hello", "World"));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_helper() {
assert_eq!(helper("foo", "bar"), "foo bar");
}
}
```
* This lets you unit test private helpers.
* The `#[cfg(test)]` attribute is only active when you run `cargo test`.

View File

@ -1,6 +1,18 @@
---
minutes: 5
---
# Unit Tests
Mark unit tests with `#[test]`:
Rust and Cargo come with a simple unit test framework:
* Unit tests are supported throughout your code.
* Integration tests are supported via the `tests/` directory.
Tests are marked with `#[test]`. Unit tests are often put in a nested `tests`
module, using `#[cfg(test)]` to conditionally compile them only when building
tests.
```rust,editable,ignore
fn first_word(text: &str) -> &str {
@ -10,20 +22,32 @@ fn first_word(text: &str) -> &str {
}
}
#[test]
fn test_empty() {
assert_eq!(first_word(""), "");
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_single_word() {
assert_eq!(first_word("Hello"), "Hello");
}
#[test]
fn test_empty() {
assert_eq!(first_word(""), "");
}
#[test]
fn test_multiple_words() {
assert_eq!(first_word("Hello World"), "Hello");
#[test]
fn test_single_word() {
assert_eq!(first_word("Hello"), "Hello");
}
#[test]
fn test_multiple_words() {
assert_eq!(first_word("Hello World"), "Hello");
}
}
```
Use `cargo test` to find and run the unit tests.
* This lets you unit test private helpers.
* The `#[cfg(test)]` attribute is only active when you run `cargo test`.
<details>
Run the tests in the playground in order to show their results.
</details>

View File

@ -1,4 +1,8 @@
## Useful crates for writing tests
---
minutes: 3
---
# Useful Crates
Rust comes with only basic support for writing tests.