Skip to content

Commit

Permalink
feat: make enums semantic
Browse files Browse the repository at this point in the history
And improve quality of error output
  • Loading branch information
Gankra committed Jul 7, 2024
1 parent 1503ced commit e9e3e24
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 92 deletions.
39 changes: 25 additions & 14 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,20 +72,23 @@ pub enum BuildError {
#[derive(Debug, thiserror::Error, Diagnostic)]
pub enum CheckFailure {
#[error(
" {func_name} {arg_kind} differed:
{arg_kind:<6}: {arg_name}: {arg_ty_name}
field : {val_path}: {val_ty_name}
expect: {expected:02X?}
caller: {caller:02X?}
callee: {callee:02X?}"
" func {func_name}'s values differed
values (native-endian hex bytes):
expect: {}
caller: {}
callee: {}
the value was {val_path}: {val_ty_name}
whose arg was {arg_name}: {arg_ty_name}",
fmt_bytes(expected),
fmt_bytes(caller),
fmt_bytes(callee)
)]
ValMismatch {
func_idx: usize,
arg_idx: usize,
val_idx: usize,
func_name: String,
arg_name: String,
arg_kind: String,
arg_ty_name: String,
val_path: String,
val_ty_name: String,
Expand All @@ -94,20 +97,20 @@ pub enum CheckFailure {
callee: Vec<u8>,
},
#[error(
" {func_name} {arg_kind} had unexpected tagged variant:
{arg_kind:<6}: {arg_name}: {arg_ty_name}
field : {val_path}: {val_ty_name}
expect: {expected}
caller: {caller}
callee: {callee}"
" func {func_name}'s value had unexpected variant
values:
expect: {expected}
caller: {caller}
callee: {callee}
the value was {val_path}: {val_ty_name}
whose arg was {arg_name}: {arg_ty_name}"
)]
TagMismatch {
func_idx: usize,
arg_idx: usize,
val_idx: usize,
func_name: String,
arg_name: String,
arg_kind: String,
arg_ty_name: String,
val_path: String,
val_ty_name: String,
Expand Down Expand Up @@ -136,3 +139,11 @@ pub enum RunError {
#[error("test impl called write_val on func {func} val {val} twice")]
DoubleWrite { func: usize, val: usize },
}

fn fmt_bytes(bytes: &[u8]) -> String {
bytes
.iter()
.map(|b| format!("{b:02X}"))
.collect::<Vec<_>>()
.join(" ")
}
125 changes: 81 additions & 44 deletions src/harness/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,12 @@ impl TestHarness {
info!("Test {subtest_name:width$} passed", width = max_name_len);
}
Err(e) => {
info!("Test {subtest_name:width$} failed!", width = max_name_len);
info!("{}", e);
let red = console::Style::new().red();
let message = format!(
"Test {subtest_name:width$} failed!\n{e}",
width = max_name_len
);
info!("{}", red.apply_to(message));
}
}
}
Expand Down Expand Up @@ -116,54 +120,40 @@ impl TestHarness {
callee_val: &ValBuffer,
) -> Result<(), CheckFailure> {
let types = &test.types;
let func = expected_val.func();
let arg = expected_val.arg();
// Enums and Taggeds are "fake" fields representing the semantic value (tag).
// In this case showing the bytes doesn't make sense, so show the Variant name
// (although we get bytes here they're the array index into the variant,
// a purely magical value that only makes sense to the harness itself!).
//
// Also we use u32::MAX to represent a poison "i dunno what it is, but it's
// definitely not the One variant we statically expected!", so most of the
// time we're here to print <other variant> and shrug.
if let Ty::Tagged(tagged_ty) = types.realize_ty(expected_val.ty) {
// This value is "fake" and is actually the semantic tag of tagged union.
// In this case showing the bytes doesn't make sense, so show the Variant name
// (although we get bytes here they're the array index into the variant,
// a purely magical value that only makes sense to the harness itself!).
//
// Also we use u32::MAX to represent a poison "i dunno what it is, but it's
// definitely not the One variant we statically expected!", so most of the
// time we're here to print <other variant> and shrug.
let expected_tag = expected_val.generate_idx(tagged_ty.variants.len());
let caller_tag =
u32::from_ne_bytes(<[u8; 4]>::try_from(&caller_val.bytes[..4]).unwrap()) as usize;
let callee_tag =
u32::from_ne_bytes(<[u8; 4]>::try_from(&callee_val.bytes[..4]).unwrap()) as usize;
let caller_tag = load_tag(caller_val);
let callee_tag = load_tag(callee_val);

if caller_tag != expected_tag || callee_tag != expected_tag {
let expected = tagged_variant_name(tagged_ty, expected_tag);
let caller = tagged_variant_name(tagged_ty, caller_tag);
let callee = tagged_variant_name(tagged_ty, callee_tag);
return Err(tag_error(types, &expected_val, expected, caller, callee));
}
} else if let Ty::Enum(enum_ty) = types.realize_ty(expected_val.ty) {
let expected_tag = expected_val.generate_idx(enum_ty.variants.len());
let caller_tag = load_tag(caller_val);
let callee_tag = load_tag(callee_val);

if caller_tag != expected_tag || callee_tag != expected_tag {
let expected = tagged_ty.variants[expected_tag].name.to_string();
let caller = tagged_ty
.variants
.get(caller_tag)
.map(|v| v.name.as_str())
.unwrap_or("<other variant>")
.to_owned();
let callee = tagged_ty
.variants
.get(callee_tag)
.map(|v| v.name.as_str())
.unwrap_or("<other variant>")
.to_owned();
return Err(CheckFailure::TagMismatch {
func_idx: expected_val.func_idx,
arg_idx: expected_val.arg_idx,
val_idx: expected_val.val_idx,
arg_kind: "argument".to_owned(),
func_name: func.func_name.to_string(),
arg_name: arg.arg_name.to_string(),
arg_ty_name: types.format_ty(arg.ty),
val_path: expected_val.path.to_string(),
val_ty_name: types.format_ty(expected_val.ty),
expected,
caller,
callee,
});
let expected = enum_variant_name(enum_ty, expected_tag);
let caller = enum_variant_name(enum_ty, caller_tag);
let callee = enum_variant_name(enum_ty, callee_tag);
return Err(tag_error(types, &expected_val, expected, caller, callee));
}
} else if caller_val.bytes != callee_val.bytes {
// General case, just get a pile of bytes to span both values
let func = expected_val.func();
let arg = expected_val.arg();
let mut expected = vec![0; caller_val.bytes.len().max(callee_val.bytes.len())];
expected_val.fill_bytes(&mut expected);
// FIXME: this doesn't do the right thing for enums
Expand All @@ -172,7 +162,6 @@ impl TestHarness {
func_idx: expected_val.func_idx,
arg_idx: expected_val.arg_idx,
val_idx: expected_val.val_idx,
arg_kind: "argument".to_owned(),
func_name: func.func_name.to_string(),
arg_name: arg.arg_name.to_string(),
arg_ty_name: types.format_ty(arg.ty),
Expand All @@ -187,3 +176,51 @@ impl TestHarness {
Ok(())
}
}

fn load_tag(val: &ValBuffer) -> usize {
u32::from_ne_bytes(<[u8; 4]>::try_from(&val.bytes[..4]).unwrap()) as usize
}

fn tagged_variant_name(tagged_ty: &kdl_script::types::TaggedTy, tag: usize) -> String {
let tagged_name = &tagged_ty.name;
let variant_name = tagged_ty
.variants
.get(tag)
.map(|v| v.name.as_str())
.unwrap_or("<other variant>");
format!("{tagged_name}::{variant_name}")
}

fn enum_variant_name(enum_ty: &kdl_script::types::EnumTy, tag: usize) -> String {
let enum_name = &enum_ty.name;
let variant_name = enum_ty
.variants
.get(tag)
.map(|v| v.name.as_str())
.unwrap_or("<other variant>");
format!("{enum_name}::{variant_name}")
}

fn tag_error(
types: &kdl_script::TypedProgram,
expected_val: &ValueRef,
expected: String,
caller: String,
callee: String,
) -> CheckFailure {
let func = expected_val.func();
let arg = expected_val.arg();
CheckFailure::TagMismatch {
func_idx: expected_val.func_idx,
arg_idx: expected_val.arg_idx,
val_idx: expected_val.val_idx,
func_name: func.func_name.to_string(),
arg_name: arg.arg_name.to_string(),
arg_ty_name: types.format_ty(arg.ty),
val_path: expected_val.path.to_string(),
val_ty_name: types.format_ty(expected_val.ty),
expected,
caller,
callee,
}
}
42 changes: 20 additions & 22 deletions src/log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ use tracing_subscriber::Layer;

use std::sync::{Arc, Mutex};

use crate::fivemat::Fivemat;

const TRACE_TEST_SPAN: &str = "test";
const IGNORE_LIST: &[&str] = &[];
const INDENT: &str = " ";

/// An in-memory logger that lets us view particular
/// spans of the logs, and understands minidump-stackwalk's
Expand Down Expand Up @@ -155,50 +158,43 @@ impl MapLogger {
write!(output, "{:indent$}", "", indent = depth * 4).unwrap();
}
fn print_span_recursive(
output: &mut String,
f: &mut Fivemat,
sub_spans: &LinkedHashMap<SpanId, SpanEntry>,
depth: usize,
span: &SpanEntry,
range: Option<Range<usize>>,
) {
if !span.name.is_empty() {
print_indent(output, depth);
let style = Style::new().blue();
write!(output, "{}", style.apply_to(&span.name)).unwrap();
write!(f, "{}", style.apply_to(&span.name)).unwrap();
for (key, val) in &span.fields {
if key == "id" {
write!(output, " {}", style.apply_to(val)).unwrap();
write!(f, " {}", style.apply_to(val)).unwrap();
} else {
write!(output, "{key}: {val}").unwrap();
write!(f, "{key}: {val}").unwrap();
}
}
writeln!(output).unwrap();
writeln!(f).unwrap();
}

let event_range = if let Some(range) = range {
&span.events[range]
} else {
&span.events[..]
};
f.add_indent(1);
for event in event_range {
match event {
EventEntry::Message(event) => {
if event.fields.contains_key("message") {
print_indent(output, depth + 1);
print_event(output, event);
print_event(f, event);
}
}
EventEntry::Span(sub_span) => {
print_span_recursive(
output,
sub_spans,
depth + 1,
&sub_spans[sub_span],
None,
);
print_span_recursive(f, sub_spans, &sub_spans[sub_span], None);
}
}
}
f.sub_indent(1);
}

let mut log = self.state.lock().unwrap();
Expand All @@ -209,28 +205,30 @@ impl MapLogger {
}
log.last_query = Some(query);

let mut output = String::new();
let mut output_buf = String::new();
let mut f = Fivemat::new(&mut output_buf, INDENT);

let (span_to_print, range) = match query {
Query::All => (&log.root_span, None),
Query::Span(span_id) => (&log.sub_spans[&span_id], None),
};

print_span_recursive(&mut output, &log.sub_spans, 0, span_to_print, range);
print_span_recursive(&mut f, &log.sub_spans, span_to_print, range);

let result = Arc::new(output);
let result = Arc::new(output_buf);
log.cur_string = Some(result.clone());
result
}
}

fn immediate_event(event: &MessageEntry) {
let mut output = String::new();
print_event(&mut output, event);
let mut f = Fivemat::new(&mut output, INDENT);
print_event(&mut f, event);
eprintln!("{}", output);
}

fn print_event(output: &mut String, event: &MessageEntry) {
fn print_event(f: &mut Fivemat, event: &MessageEntry) {
use std::fmt::Write;
if let Some(message) = event.fields.get("message") {
let style = match event.level {
Expand All @@ -241,7 +239,7 @@ fn print_event(output: &mut String, event: &MessageEntry) {
Level::TRACE => Style::new().green(),
};
// writeln!(output, "[{:5}] {}", event.level, message).unwrap();
writeln!(output, "{}", style.apply_to(message)).unwrap();
writeln!(f, "{}", style.apply_to(message)).unwrap();
}
}

Expand Down
Loading

0 comments on commit e9e3e24

Please sign in to comment.