mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-16 22:37:49 +00:00
Rewrite CompareMessage to check the whole string (#611)
Not just a single string component like before.
This commit is contained in:
committed by
GitHub
parent
6db5ad78ab
commit
99f8d2cfe7
@@ -923,31 +923,38 @@ Checking the reported panic message is a bit more complicated. The reason is tha
|
|||||||
|
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
|
||||||
/// Compares a `fmt::Arguments` instance with the `MESSAGE` string.
|
/// Compares a `fmt::Arguments` instance with the `MESSAGE` string
|
||||||
///
|
///
|
||||||
/// To use this type, write the `fmt::Arguments` instance to it using the
|
/// To use this type, write the `fmt::Arguments` instance to it using the
|
||||||
/// `write` macro. If a message component matches `MESSAGE`, the equals
|
/// `write` macro. If the message component matches `MESSAGE`, the `expected`
|
||||||
/// field is set to true.
|
/// field is the empty string.
|
||||||
struct CompareMessage {
|
struct CompareMessage {
|
||||||
equals: bool,
|
expected: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Write for CompareMessage {
|
impl fmt::Write for CompareMessage {
|
||||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
if s == MESSAGE {
|
if self.expected.starts_with(s) {
|
||||||
self.equals = true;
|
self.expected = &self.expected[s.len()..];
|
||||||
|
} else {
|
||||||
|
fail("message not equal to expected message");
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The trick is to implement the [`fmt::Write`] trait, which is called by the [`write`] macro with `&str` arguments. This makes it possible to compare the panic arguments with `MESSAGE`. By the way, this is the same trait that we implemented for our VGA buffer writer in order to print to the screen.
|
The trick is to implement the [`fmt::Write`] trait like we did for our [VGA buffer writer]. The [`write_str`] method is called with a `&str` parameter that we can compare with the expected message. An important detail is that the method is called _multiple times_ with the individual string components. For example, when we do `print!("{}z", "xy")` the method on our VGA buffer writer is invoked once with the string `"xy"` and once with the string `"z"`.
|
||||||
|
|
||||||
[`fmt::Write`]: https://doc.rust-lang.org/core/fmt/trait.Write.html
|
[`fmt::Write`]: https://doc.rust-lang.org/core/fmt/trait.Write.html
|
||||||
[`write`]: https://doc.rust-lang.org/core/macro.write.html
|
[VGA buffer writer]: ./second-edition/posts/03-vga-text-buffer/index.md#formatting-macros
|
||||||
|
[`write_str`]: https://doc.rust-lang.org/core/fmt/trait.Write.html#tymethod.write_str
|
||||||
|
|
||||||
|
This means that we can't directly compare the `s` argument with the expected message, since it might only be a substring. Instead, we use the [`starts_with`] method to verify that the given string component is a substring of the expected message. Then we use [string slicing] to remove the already printed characters from the `expected` string. If the `expected` field is an empty string after writing the panic message, it means that it matches the expected message.
|
||||||
|
|
||||||
|
[`starts_with`]: https://doc.rust-lang.org/std/primitive.str.html#method.starts_with
|
||||||
|
[string slicing]: https://doc.rust-lang.org/book/ch04-03-slices.html#string-slices
|
||||||
|
|
||||||
The above code only works for messages with a single component. This means that it works for `panic!("some message")`, but not for `panic!("some {}", message)`. This isn't ideal, but good enough for our test.
|
|
||||||
|
|
||||||
With the `CompareMessage` type, we can finally implement our `check_message` function:
|
With the `CompareMessage` type, we can finally implement our `check_message` function:
|
||||||
|
|
||||||
@@ -960,11 +967,11 @@ use core::fmt::Write;
|
|||||||
|
|
||||||
fn check_message(info: &PanicInfo) {
|
fn check_message(info: &PanicInfo) {
|
||||||
let message = info.message().unwrap_or_else(|| fail("no message"));
|
let message = info.message().unwrap_or_else(|| fail("no message"));
|
||||||
let mut compare_message = CompareMessage { equals: false };
|
let mut compare_message = CompareMessage { expected: MESSAGE };
|
||||||
write!(&mut compare_message, "{}", message)
|
write!(&mut compare_message, "{}", message)
|
||||||
.unwrap_or_else(|_| fail("write failed"));
|
.unwrap_or_else(|_| fail("write failed"));
|
||||||
if !compare_message.equals {
|
if !compare_message.expected.is_empty() {
|
||||||
fail("message not equal to expected message");
|
fail("message shorter than expected message");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -973,7 +980,9 @@ The function uses the [`PanicInfo::message`] function to get the panic message.
|
|||||||
|
|
||||||
[`PanicInfo::message`]: https://doc.rust-lang.org/core/panic/struct.PanicInfo.html#method.message
|
[`PanicInfo::message`]: https://doc.rust-lang.org/core/panic/struct.PanicInfo.html#method.message
|
||||||
|
|
||||||
After querying the message, the function constructs a `CompareMessage` instance and writes the message to it using the `write!` macro. Afterwards it reads the `equals` field and fails the test if the panic message does not equal `MESSAGE`.
|
After querying the message, the function constructs a `CompareMessage` instance with the `expected` field set to the `MESSAGE` string. Then it writes the message to it using the [`write!`] macro. After the write, it reads the `expected` field and fails the test if it is not the empty string.
|
||||||
|
|
||||||
|
[`write!`]: https://doc.rust-lang.org/core/macro.write.html
|
||||||
|
|
||||||
Now we can run the test using `cargo xtest --test panic_handler`. We see that it passes, which means that the reported panic info is correct. If we use a wrong line number in `PANIC_LINE` or panic with an additional character through `panic!("{}x", MESSAGE)`, we see that the test indeed fails.
|
Now we can run the test using `cargo xtest --test panic_handler`. We see that it passes, which means that the reported panic info is correct. If we use a wrong line number in `PANIC_LINE` or panic with an additional character through `panic!("{}x", MESSAGE)`, we see that the test indeed fails.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user