1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
|
// Note: It's recommended, not to change anything above or below (see big comment below)
use color_eyre::{eyre::Report, Section};
#[rustfmt::skip]
#[derive(Debug, thiserror::Error)]
#[error("{0}")]
struct TestError(&'static str);
#[rustfmt::skip]
#[tracing::instrument]
fn get_error(msg: &'static str) -> Report {
#[rustfmt::skip]
#[inline(never)]
fn create_report(msg: &'static str) -> Report {
Report::msg(msg)
.note("note")
.warning("warning")
.suggestion("suggestion")
.error(TestError("error"))
}
// Using `Option` to trigger `is_dependency_code`.
// See https://github.com/eyre-rs/color-eyre/blob/4ddaeb2126ed8b14e4e6aa03d7eef49eb8561cf0/src/config.rs#L56
None::<Option<()>>.ok_or_else(|| create_report(msg)).unwrap_err()
}
#[cfg(all(not(feature = "track-caller"), not(feature = "capture-spantrace"),))]
static ERROR_FILE_NAME: &str = "theme_error_control_minimal.txt";
#[cfg(all(feature = "track-caller", not(feature = "capture-spantrace"),))]
static ERROR_FILE_NAME: &str = "theme_error_control_location.txt";
#[cfg(all(not(feature = "track-caller"), feature = "capture-spantrace",))]
static ERROR_FILE_NAME: &str = "theme_error_control_spantrace.txt";
#[cfg(all(feature = "capture-spantrace", feature = "track-caller",))]
static ERROR_FILE_NAME: &str = "theme_error_control.txt";
//#[test]
#[cfg(not(miri))]
fn test_error_backwards_compatibility() {
setup();
let error = get_error("test");
/*
Note: If you change anything above this comment, it could make the stored test data invalid (because the structure of the generated error might change). In most cases, small changes shouldn't be a problem, but keep this in mind if you change something and suddenly this test case fails.
The empty lines at the beginning are needed because `color_eyre` sometimes seems to not be able to find the correct line of source and uses the first line of the module (plus the next four lines).
If a change of the code above leads to incompatibility, you therefore have to backport this (changed) file to the version of `color_eyre` that you want to test against and execute it to generate new control test data.
To do this, do the following:
1) Change this file, and if the test now fails do:
2) Checkout the `color_eyre` version from Git that you want to test against
3) Add this test file to '/tests'
4) If `error_file_path` or `panic_file_path` exist (see below), delete these files
5) If you now run this test, it will fail and generate test data files in the current working directory
6) copy these files to `error_file_path` and `panic_file_path` in the current version of `color_eyre` (see the instructions that are printed out in step 5)
Now this test shouldn't fail anymore in the current version.
Alternatively, you also could just regenerate the test data of the current repo (as described above, but without backporting), and use this test data from now on (this makes sense, if you only changed the above code, and nothing else that could lead to the test failing).
# How the tests in this file work:
1) generate a error (for example, with the code above)
2) convert this error to a string
3) load stored error data to compare to (stored in `error_file_path` and `panic_file_path`)
4) if `error_file_path` and/or `panic_file_path` doesn't exist, generate corresponding files in the current working directory and request the user to fix the issue (see below)
5) extract ANSI escaping sequences (of controls and current errors)
6) compare if the current error and the control contains the same ANSI escape sequences
7) If not, fail and show the full strings of the control and the current error
Below you'll find instructions about how to debug failures of the tests in this file
*/
let target = format!("{:?}", error);
test_backwards_compatibility(target, ERROR_FILE_NAME)
}
#[cfg(not(feature = "capture-spantrace"))]
static PANIC_FILE_NAME: &str = "theme_panic_control_no_spantrace.txt";
#[cfg(feature = "capture-spantrace")]
static PANIC_FILE_NAME: &str = "theme_panic_control.txt";
// The following tests the installed panic handler
//#[test]
#[allow(unused_mut)]
#[allow(clippy::vec_init_then_push)]
#[cfg(not(miri))]
fn test_panic_backwards_compatibility() {
let mut features: Vec<&str> = vec![];
#[cfg(feature = "capture-spantrace")]
features.push("capture-spantrace");
#[cfg(feature = "issue-url")]
features.push("issue-url");
#[cfg(feature = "track-caller")]
features.push("track-caller");
let features = features.join(",");
let features = if !features.is_empty() {
vec!["--features", &features]
} else {
vec![]
};
let output = std::process::Command::new("cargo")
.args(["run", "--example", "theme_test_helper"])
.arg("--no-default-features")
.args(&features)
.output()
.expect("failed to execute process");
let target = String::from_utf8(output.stderr).expect("failed to convert output to `String`");
println!("{}", target);
test_backwards_compatibility(target, PANIC_FILE_NAME)
}
/// Helper for `test_error` and `test_panic`
fn test_backwards_compatibility(target: String, file_name: &str) {
use ansi_parser::{AnsiParser, AnsiSequence, Output};
use owo_colors::OwoColorize;
use std::{fs, path::Path};
let file_path = ["tests/data/", file_name].concat();
// If `file_path` is missing, save corresponding file to current working directory, and panic with the request to move the file to `file_path`, and to commit it to Git. Being explicit (instead of saving directly to `file_path`) to make sure `file_path` is committed to Git.
if !Path::new(&file_path).is_file() {
std::fs::write(file_name, &target)
.expect("\n\nError saving missing `control target` to a file");
panic!("Required test data missing! Fix this, by moving '{}' to '{}', and commit it to Git.\n\nNote: '{0}' was just generated in the current working directory.\n\n", file_name, file_path);
}
// `unwrap` should never fail with files generated by this function
let control = String::from_utf8(fs::read(file_path).unwrap()).unwrap();
fn split_ansi_output(input: &str) -> (Vec<Output>, Vec<AnsiSequence>) {
let all: Vec<_> = input.ansi_parse().collect();
let ansi: Vec<_> = input
.ansi_parse()
.filter_map(|x| {
if let Output::Escape(ansi) = x {
Some(ansi)
} else {
None
}
})
.collect();
(all, ansi)
}
fn normalize_backtrace(input: &str) -> String {
input
.lines()
.take_while(|v| !v.contains("core::panic"))
.collect::<Vec<_>>()
.join("\n")
}
let control = normalize_backtrace(&control);
let target = normalize_backtrace(&target);
let (_control_tokens, control_ansi) = split_ansi_output(&control);
let (_target_tokens, target_ansi) = split_ansi_output(&target);
fn section(title: &str, content: impl AsRef<str>) -> String {
format!(
"{}\n{}",
format!("-------- {title} --------").red(),
content.as_ref()
)
}
// pretty_assertions::assert_eq!(target, control);
let msg = [
// comment out / un-comment what you need or don't need for debugging (see below for more instructions):
format!("{}", "\x1b[0m\n\nANSI escape sequences are not identical to control!".red()),
// ^ `\x1b[0m` clears previous ANSI escape sequences
section("CONTROL STRING", &control),
// section("CONTROL DEBUG STRING", format!("{control:?}")),
// section("CONTROL ANSI PARSER OUTPUT", format!("{_control_tokens:?}")),
// section("CONTROL ANSI PARSER ANSI", format!("{control_ansi:?}")),
section("CURRENT STRING", &target),
// section("CURRENT DEBUG STRING", format!("{target:?}")),
// section("CURRENT ANSI PARSER OUTPUT", format!("{_target_tokens:?}")),
// section("CURRENT ANSI PARSER ANSI", format!("{target_ansi:?}")),
format!("{}", "See the src of this test for more information about the test and ways to include/exclude debugging information.\n\n".red()),
].join("\n\n");
pretty_assertions::assert_eq!(target_ansi, control_ansi, "{}", &msg);
/*
# Tips for debugging test failures
It's a bit a pain to find the reason for test failures. To make it as easy as possible, I recommend the following workflow:
## Compare the actual errors
1) Run the test in two terminals with "CONTROL STRING" and "CURRENT STRING" active
2) In on terminal have the output of "CONTROL STRING" visible, in the out that of "CURRENT STRING"
3) Make sure, both errors are at the same location of their terminal
4) Now switch between the two terminal rapidly and often. This way it's easy to see changes.
Note that we only compare ANSI escape sequences – so if the text changes, that is not a problem.
A problem would it be, if there is a new section of content (which might contain new ANSI escape sequences). This could happen, for example, if the current code produces warnings, etc. (especially, with the panic handler test).
## Compare `ansi_parser` tokens
If you fixed all potential problems above, and the test still failes, compare the actual ANSI escape sequences:
1) Activate "CURRENT ANSI PARSER OUTPUT" and "CURRENT ANSI PARSER OUTPUT" above
2) Copy this output to a text editor and replace all "), " with ")," + a newline (this way every token is on its own line)
3) Compare this new output with a diff tool (https://meldmerge.org/ is a nice option with a GUI)
With this approach, you should see what has changed. Just remember that we only compare the ANSI escape sequences, text is skipped. With "CURRENT ANSI PARSER OUTPUT" and "CURRENT ANSI PARSER OUTPUT", however, text tokens are shown as well (to make it easier to figure out the source of ANSI escape sequences.)
*/
}
fn setup() {
std::env::set_var("RUST_LIB_BACKTRACE", "1");
#[cfg(feature = "capture-spantrace")]
{
use tracing_subscriber::prelude::*;
use tracing_subscriber::{fmt, EnvFilter};
let fmt_layer = fmt::layer().with_target(false);
let filter_layer = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info"))
.unwrap();
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.with(tracing_error::ErrorLayer::default())
.init();
}
color_eyre::install().expect("Failed to install `color_eyre`");
/*
# Easy way to test styles
1) uncomment the last line
2) activate the following code
3) change the styles
4) run this test via `cargo test test_error_backwards_compatibility --test styles`
5) your new styled error will be below the output "CURRENT STRING ="
6) if there is not such output, search for "CURRENT STRING =" above, and activate the line
7) if you are interested in running this test for actual testing this crate, don't forget to uncomment the code below, activate the above line
*/
/*
use owo_colors::style;
let styles = color_eyre::config::Styles::dark()
// ^ or, instead of `dark`, use `new` for blank styles or `light` if you what to derive from a light theme. Now configure your styles (see the docs for all options):
.line_number(style().blue())
.help_info_suggestion(style().red());
color_eyre::config::HookBuilder::new()
.styles(styles)
.install()
.expect("Failed to install `color_eyre`");
*/
}
|