microsoft/qdk
Publicmirrored fromhttps://github.com/microsoft/qdkAvailable
compiler/qsc_frontend/src/error/tests.rs
196lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. |
| 2 | // Licensed under the MIT License. |
| 3 | |
| 4 | use super::WithSource; |
| 5 | use crate::compile::SourceMap; |
| 6 | use expect_test::expect; |
| 7 | use miette::Diagnostic; |
| 8 | use qsc_data_structures::span::Span; |
| 9 | use std::{error::Error, fmt::Write, iter, str::from_utf8}; |
| 10 | use thiserror::Error; |
| 11 | |
| 12 | #[derive(Clone, Debug, Diagnostic, Error)] |
| 13 | enum TestError { |
| 14 | #[error("Error: {0}")] |
| 15 | #[diagnostic(code("Qsc.Test.Error.NoSpans"))] |
| 16 | NoSpans(String), |
| 17 | #[error("Error: {0}")] |
| 18 | #[diagnostic(code("Qsc.Test.Error.TwoSpans"))] |
| 19 | TwoSpans( |
| 20 | String, |
| 21 | #[label("first label")] Span, |
| 22 | #[label("second label")] Span, |
| 23 | ), |
| 24 | } |
| 25 | |
| 26 | #[test] |
| 27 | fn no_files() { |
| 28 | let sources = SourceMap::default(); |
| 29 | let error = TestError::NoSpans("value".into()); |
| 30 | let formatted_error = format_error(&WithSource::from_map(&sources, error)); |
| 31 | |
| 32 | expect![[r#" |
| 33 | Error: value |
| 34 | "#]] |
| 35 | .assert_eq(&formatted_error); |
| 36 | } |
| 37 | |
| 38 | #[test] |
| 39 | fn error_spans_two_files() { |
| 40 | let test1_contents = "namespace Foo {}"; |
| 41 | let test2_contents = "namespace Bar {}"; |
| 42 | let mut sources = SourceMap::default(); |
| 43 | let test1_offset = sources.push("test1.qs".into(), test1_contents.into()); |
| 44 | let test2_offset = sources.push("test2.qs".into(), test2_contents.into()); |
| 45 | |
| 46 | let error = TestError::TwoSpans( |
| 47 | "value".into(), |
| 48 | span_with_offset(test1_offset, 10, 13), |
| 49 | span_with_offset(test2_offset, 10, 13), |
| 50 | ); |
| 51 | |
| 52 | let formatted_error = format_error(&WithSource::from_map(&sources, error)); |
| 53 | |
| 54 | expect![[r#" |
| 55 | Error: value |
| 56 | [first label] [test1.qs] [Foo] |
| 57 | [second label] [test2.qs] [Bar] |
| 58 | "#]] |
| 59 | .assert_eq(&formatted_error); |
| 60 | } |
| 61 | |
| 62 | #[test] |
| 63 | fn error_spans_begin() { |
| 64 | let test1_contents = "namespace Foo {}"; |
| 65 | let test2_contents = "namespace Bar {}"; |
| 66 | let mut sources = SourceMap::default(); |
| 67 | let test1_offset = sources.push("test1.qs".into(), test1_contents.into()); |
| 68 | let test2_offset = sources.push("test2.qs".into(), test2_contents.into()); |
| 69 | |
| 70 | let error = TestError::TwoSpans( |
| 71 | "value".into(), |
| 72 | span_with_offset(test1_offset, 0, 13), |
| 73 | span_with_offset(test2_offset, 0, 13), |
| 74 | ); |
| 75 | |
| 76 | let formatted_error = format_error(&WithSource::from_map(&sources, error)); |
| 77 | |
| 78 | expect![[r#" |
| 79 | Error: value |
| 80 | [first label] [test1.qs] [namespace Foo] |
| 81 | [second label] [test2.qs] [namespace Bar] |
| 82 | "#]] |
| 83 | .assert_eq(&formatted_error); |
| 84 | } |
| 85 | |
| 86 | #[allow(clippy::cast_possible_truncation)] |
| 87 | #[test] |
| 88 | fn error_spans_eof() { |
| 89 | let test1_contents = "namespace Foo {}"; |
| 90 | let test2_contents = "namespace Bar {}"; |
| 91 | let mut sources = SourceMap::default(); |
| 92 | let test1_offset = sources.push("test1.qs".into(), test1_contents.into()); |
| 93 | let test2_offset = sources.push("test2.qs".into(), test2_contents.into()); |
| 94 | |
| 95 | let error = TestError::TwoSpans( |
| 96 | "value".into(), |
| 97 | span_with_offset( |
| 98 | test1_offset, |
| 99 | test1_contents.len() as u32, |
| 100 | test1_contents.len() as u32, |
| 101 | ), |
| 102 | span_with_offset( |
| 103 | test2_offset, |
| 104 | test2_contents.len() as u32, |
| 105 | test2_contents.len() as u32, |
| 106 | ), |
| 107 | ); |
| 108 | |
| 109 | let formatted_error = format_error(&WithSource::from_map(&sources, error)); |
| 110 | |
| 111 | expect![[r#" |
| 112 | Error: value |
| 113 | [first label] [test1.qs] [] |
| 114 | [second label] [test2.qs] [] |
| 115 | "#]] |
| 116 | .assert_eq(&formatted_error); |
| 117 | } |
| 118 | |
| 119 | #[test] |
| 120 | fn resolve_spans() { |
| 121 | let test1_contents = "namespace Foo {}"; |
| 122 | let test2_contents = "namespace Bar {}"; |
| 123 | let mut sources = SourceMap::default(); |
| 124 | let test1_offset = sources.push("test1.qs".into(), test1_contents.into()); |
| 125 | let test2_offset = sources.push("test2.qs".into(), test2_contents.into()); |
| 126 | |
| 127 | let error = TestError::TwoSpans( |
| 128 | "value".into(), |
| 129 | span_with_offset(test1_offset, 10, 13), |
| 130 | span_with_offset(test2_offset, 10, 13), |
| 131 | ); |
| 132 | |
| 133 | let with_source = WithSource::from_map(&sources, error); |
| 134 | |
| 135 | let resolved_spans = with_source |
| 136 | .labels() |
| 137 | .expect("expected labels to exist") |
| 138 | .map(|l| { |
| 139 | let resolved = with_source.resolve_span(l.inner()); |
| 140 | ( |
| 141 | resolved.0.name.to_string(), |
| 142 | resolved.1.offset(), |
| 143 | resolved.1.len(), |
| 144 | ) |
| 145 | }) |
| 146 | .collect::<Vec<_>>(); |
| 147 | |
| 148 | expect![[r#" |
| 149 | [ |
| 150 | ( |
| 151 | "test1.qs", |
| 152 | 10, |
| 153 | 3, |
| 154 | ), |
| 155 | ( |
| 156 | "test2.qs", |
| 157 | 10, |
| 158 | 3, |
| 159 | ), |
| 160 | ] |
| 161 | "#]] |
| 162 | .assert_debug_eq(&resolved_spans); |
| 163 | } |
| 164 | |
| 165 | fn span_with_offset(offset: u32, lo: u32, hi: u32) -> Span { |
| 166 | Span { |
| 167 | lo: lo + offset, |
| 168 | hi: hi + offset, |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | fn format_error(error: &WithSource<TestError>) -> String { |
| 173 | let mut s = String::new(); |
| 174 | write!(s, "{error}").expect("writing should succeed"); |
| 175 | for e in iter::successors(error.source(), |&e| e.source()) { |
| 176 | write!(s, ": {e}").expect("writing should succeed"); |
| 177 | } |
| 178 | for label in error.labels().into_iter().flatten() { |
| 179 | let span = error |
| 180 | .source_code() |
| 181 | .expect("expected valid source code") |
| 182 | .read_span(label.inner(), 0, 0) |
| 183 | .expect("expected to be able to read span"); |
| 184 | |
| 185 | write!( |
| 186 | s, |
| 187 | "\n [{}] [{}] [{}]", |
| 188 | label.label().unwrap_or(""), |
| 189 | span.name().expect("expected source file name"), |
| 190 | from_utf8(span.data()).expect("expected valid utf-8 string"), |
| 191 | ) |
| 192 | .expect("writing should succeed"); |
| 193 | } |
| 194 | writeln!(s).expect("writing should succeed"); |
| 195 | s |
| 196 | } |
| 197 | |