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