You've already forked comprehensive-rust
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:
committed by
GitHub
parent
ea204774b6
commit
6d19292f16
10
src/testing/Cargo.toml
Normal file
10
src/testing/Cargo.toml
Normal 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
37
src/testing/exercise.md
Normal 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
126
src/testing/exercise.rs
Normal 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 "));
|
||||
}
|
||||
}
|
@ -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
35
src/testing/lints.md
Normal 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>
|
@ -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
5
src/testing/solution.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Solution
|
||||
|
||||
```rust,editable
|
||||
{{#include exercise.rs:solution}}
|
||||
```
|
@ -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`.
|
@ -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>
|
||||
|
@ -1,4 +1,8 @@
|
||||
## Useful crates for writing tests
|
||||
---
|
||||
minutes: 3
|
||||
---
|
||||
|
||||
# Useful Crates
|
||||
|
||||
Rust comes with only basic support for writing tests.
|
||||
|
||||
|
Reference in New Issue
Block a user