Skip to content

Commit

Permalink
More thoughtful and consistent test/main split
Browse files Browse the repository at this point in the history
  • Loading branch information
djmitche committed Feb 18, 2025
1 parent fa2124d commit 31394a9
Show file tree
Hide file tree
Showing 19 changed files with 66 additions and 24 deletions.
20 changes: 20 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,26 @@ the [instructions in the README].

[instructions in the README]: README.md#building

## Writing Exercises

Each segment ends with an exercise. Exercises are typically structured as an
`exercise.rs` containing the problem and solution. This is referenced from
`exercise.md` and `solution.md`, using `{{#include exercise.rs:anchor_name}}` to
match ANCHOR comments in the `exercise.rs` file. Each segment also has a
`Cargo.toml` file containing a `[[bin]]` section referring to `exercise.rs`, and
that Cargo package is referenced from the workspace the root `Cargo.toml`. The
result is that `exercise.rs` is built and tested by `cargo test`.

For segments on day 1, exercises should use `fn main() { .. }` and `println!`,
with students visually verifying the correct output. On subsequent days, prefer
tests and omit `fn main() { .. }`. However, where tests would be difficult and
visual verification is more natural (such as in the Logger exercise), using
`fn main { .. }` is OK.

Especially for exercises without tests, consider including tests in
`exercise.rs` that do not appear in either `exercise.md` or `solution.md`, as
these can ensure the solution is correct.

## Testing

We test the course material in several ways:
Expand Down
2 changes: 1 addition & 1 deletion src/borrowing/exercise.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ minutes: 20
Copy the code below to <https://play.rust-lang.org/> and fill in the missing
method:

```rust
```rust,editable
{{#include ../../third_party/rust-on-exercism/health-statistics.rs:setup}}
{{#include ../../third_party/rust-on-exercism/health-statistics.rs:User_visit_doctor}}
Expand Down
2 changes: 1 addition & 1 deletion src/closures/exercise.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Building on the generic logger from this morning, implement a `Filter` which
uses a closure to filter log messages, sending those which pass the filtering
predicate to an inner logger.

```rust,compile_fail
```rust,compile_fail,editable
{{#include exercise.rs:setup}}
// TODO: Define and implement `Filter`.
Expand Down
2 changes: 1 addition & 1 deletion src/error-handling/exercise.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Rewrite `eval` to instead use idiomatic error handling to handle this error case
and return an error when it occurs. We provide a simple `DivideByZeroError` type
to use as the error type for `eval`.

```rust
```rust,editable
{{#include exercise.rs:types}}
{{#include exercise.rs:eval}}
Expand Down
6 changes: 3 additions & 3 deletions src/generics/exercise.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ minutes: 10
In this short exercise, you will implement a generic `min` function that
determines the minimum of two values, using the [`Ord`] trait.

```rust,compile_fail
```rust,compile_fail,editable
use std::cmp::Ordering;
// TODO: implement the `min` function used in `main`.
// TODO: implement the `min` function used in the tests.
{{#include exercise.rs:main}}
{{#include exercise.rs:tests}}
```

<details>
Expand Down
13 changes: 10 additions & 3 deletions src/generics/exercise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,22 @@ fn min<T: Ord>(l: T, r: T) -> T {
}
}

// ANCHOR: main
fn main() {
// ANCHOR: tests
#[test]
fn integers() {
assert_eq!(min(0, 10), 0);
assert_eq!(min(500, 123), 123);
}

#[test]
fn chars() {
assert_eq!(min('a', 'z'), 'a');
assert_eq!(min('7', '1'), '1');
}

#[test]
fn strings() {
assert_eq!(min("hello", "goodbye"), "goodbye");
assert_eq!(min("bat", "armadillo"), "armadillo");
}
// ANCHOR_END: main
// ANCHOR_END: tests
2 changes: 1 addition & 1 deletion src/iterators/exercise.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Copy the following code to <https://play.rust-lang.org/> and make the tests
pass. Use an iterator expression and `collect` the result to construct the
return value.

```rust
```rust,editable
{{#include exercise.rs:offset_differences}}
todo!()
}
Expand Down
2 changes: 1 addition & 1 deletion src/lifetimes/exercise.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ What remains for you is to implement the `parse_field` function and the
// TODO: Implement ProtoMessage for Person and PhoneNumber.
{{#include exercise.rs:main }}
{{#include exercise.rs:tests }}
```

<details>
Expand Down
21 changes: 17 additions & 4 deletions src/lifetimes/exercise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,21 +193,31 @@ impl<'a> ProtoMessage<'a> for PhoneNumber<'a> {
}
}

// ANCHOR: main
fn main() {
// ANCHOR: tests
#[test]
fn test_id() {
let person_id: Person = parse_message(&[0x10, 0x2a]);
assert_eq!(person_id, Person { name: "", id: 42, phone: vec![] });
}

#[test]
fn test_name() {
let person_name: Person = parse_message(&[
0x0a, 0x0e, 0x62, 0x65, 0x61, 0x75, 0x74, 0x69, 0x66, 0x75, 0x6c, 0x20,
0x6e, 0x61, 0x6d, 0x65,
]);
assert_eq!(person_name, Person { name: "beautiful name", id: 0, phone: vec![] });
}

#[test]
fn test_just_person() {
let person_name_id: Person =
parse_message(&[0x0a, 0x04, 0x45, 0x76, 0x61, 0x6e, 0x10, 0x16]);
assert_eq!(person_name_id, Person { name: "Evan", id: 22, phone: vec![] });
}

#[test]
fn test_phone() {
let phone: Person = parse_message(&[
0x0a, 0x00, 0x10, 0x00, 0x1a, 0x16, 0x0a, 0x0e, 0x2b, 0x31, 0x32, 0x33,
0x34, 0x2d, 0x37, 0x37, 0x37, 0x2d, 0x39, 0x30, 0x39, 0x30, 0x12, 0x04,
Expand All @@ -221,8 +231,11 @@ fn main() {
phone: vec![PhoneNumber { number: "+1234-777-9090", type_: "home" },],
}
);
}

// Put that all together into a single parse.
// Put that all together into a single parse.
#[test]
fn test_full_person() {
let person: Person = parse_message(&[
0x0a, 0x07, 0x6d, 0x61, 0x78, 0x77, 0x65, 0x6c, 0x6c, 0x10, 0x2a, 0x1a,
0x16, 0x0a, 0x0e, 0x2b, 0x31, 0x32, 0x30, 0x32, 0x2d, 0x35, 0x35, 0x35,
Expand All @@ -243,4 +256,4 @@ fn main() {
}
);
}
// ANCHOR_END: main
// ANCHOR_END: tests
2 changes: 1 addition & 1 deletion src/methods-and-traits/exercise.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ implementing that same trait, adding behavior in the process. In the "Generics"
segment this afternoon, we will see how to make the wrapper generic over the
wrapped type.

```rust,compile_fail
```rust,compile_fail,editable
{{#include exercise.rs:setup}}
// TODO: Implement the `Logger` trait for `VerbosityFilter`.
Expand Down
2 changes: 1 addition & 1 deletion src/modules/exercise.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ files in the `src` directory.

Here's the single-module implementation of the GUI library:

```rust
```rust,editable
{{#include exercise.rs:single-module}}
```

Expand Down
2 changes: 1 addition & 1 deletion src/pattern-matching/exercise.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ get the tests to pass one-by-one. You can also skip a test temporarily with
fn test_value() { .. }
```

```rust
```rust,editable
{{#include exercise.rs:Operation}}
{{#include exercise.rs:Expression}}
Expand Down
2 changes: 1 addition & 1 deletion src/references/exercise.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ minutes: 20
We will create a few utility functions for 3-dimensional geometry, representing
a point as `[f64;3]`. It is up to you to determine the function signatures.

```rust,compile_fail
```rust,compile_fail,editable
// Calculate the magnitude of a vector by summing the squares of its coordinates
// and taking the square root. Use the `sqrt()` method to calculate the square
// root, like `v.sqrt()`.
Expand Down
2 changes: 1 addition & 1 deletion src/smart-pointers/exercise.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Implement the following types, so that the given tests pass.
Extra Credit: implement an iterator over a binary tree that returns the values
in order.

```rust,compile_fail
```rust,compile_fail,editable
{{#include exercise.rs:types}}
// Implement `new`, `insert`, `len`, and `has` for `Subtree`.
Expand Down
2 changes: 1 addition & 1 deletion src/std-traits/exercise.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ In this example, you will implement the classic
playground, and implement the missing bits. Only rotate ASCII alphabetic
characters, to ensure the result is still valid UTF-8.

```rust,compile_fail
```rust,editable
{{#include exercise.rs:head }}
// Implement the `Read` trait for `RotDecoder`.
Expand Down
2 changes: 1 addition & 1 deletion src/testing/exercise.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ 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
```rust,editable
{{#include exercise.rs:luhn}}
{{#include exercise.rs:unit-tests}}
Expand Down
2 changes: 1 addition & 1 deletion src/tuples-and-arrays/exercise.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ transpose a matrix (turn rows into columns):
Copy the code below to <https://play.rust-lang.org/> and implement the function.
This function only operates on 3x3 matrices.

```rust,should_panic
```rust,should_panic,editable
{{#include exercise.rs:transpose}}
todo!()
}
Expand Down
2 changes: 2 additions & 0 deletions src/tuples-and-arrays/exercise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ fn main() {
// ANCHOR_END: main
// ANCHOR_END: solution

// This test does not appear in the exercise, as this is very early in the course, but it verifies
// that the solution is correct.
#[test]
fn test_transpose() {
let matrix = [
Expand Down
2 changes: 1 addition & 1 deletion src/unsafe-rust/exercise.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ The [Nomicon] also has a very useful chapter about FFI.
Copy the code below to <https://play.rust-lang.org/> and fill in the missing
functions and methods:

```rust,should_panic
```rust,should_panic,editable
// TODO: remove this when you're done with your implementation.
#![allow(unused_imports, unused_variables, dead_code)]
Expand Down

0 comments on commit 31394a9

Please sign in to comment.