⭐️ A friendly language for building type-safe, scalable systems!
1use camino::{Utf8Path, Utf8PathBuf};
2use gleam_core::{
3 io::{Content, FileSystemWriter, memory::InMemoryFileSystem},
4 version::COMPILER_VERSION,
5};
6use itertools::Itertools;
7use regex::Regex;
8use std::{collections::HashMap, fmt::Write, sync::LazyLock};
9
10#[derive(Debug)]
11pub struct TestCompileOutput {
12 pub files: HashMap<Utf8PathBuf, Content>,
13 pub warnings: Vec<gleam_core::Warning>,
14}
15
16impl TestCompileOutput {
17 pub fn as_overview_text(&self) -> String {
18 let mut buffer = String::new();
19 for (path, content) in self.files.iter().sorted_by(|a, b| a.0.cmp(b.0)) {
20 let normalised_path = if path.as_str().contains("cases") {
21 path.as_str()
22 .split("cases")
23 .skip(1)
24 .collect::<String>()
25 .as_str()
26 .replace('\\', "/")
27 .split('/')
28 .skip(1)
29 .join("/")
30 } else {
31 path.as_str().replace('\\', "/")
32 };
33 buffer.push_str("//// ");
34 buffer.push_str(&normalised_path);
35 buffer.push('\n');
36
37 let extension = path.extension();
38 match content {
39 _ if extension == Some("cache") => buffer.push_str("<.cache binary>"),
40 Content::Binary(data) => write!(buffer, "<{} byte binary>", data.len()).unwrap(),
41
42 Content::Text(_) if normalised_path.ends_with("@@main.erl") => {
43 write!(buffer, "<erlang entrypoint>").unwrap()
44 }
45
46 Content::Text(text) => {
47 let format_path = |caps: ®ex::Captures| {
48 caps.get(1)
49 .expect("file path")
50 .as_str()
51 .replace("\\\\", "/")
52 };
53 let text = FILE_LINE_REGEX.replace_all(text, |caps: ®ex::Captures| {
54 let path = format_path(caps);
55 let line_number = caps.get(2).expect("line number").as_str();
56 format!("-file(\"{path}\", {line_number}).")
57 });
58 let text = FILEPATH_MACRO_REGEX
59 .replace_all(text.to_string().as_str(), |caps: ®ex::Captures| {
60 let path = format_path(caps);
61 format!("-define(FILEPATH, \"{path}\").")
62 })
63 .replace(COMPILER_VERSION, "<gleam compiler version string>");
64 buffer.push_str(&text)
65 }
66 };
67 buffer.push('\n');
68 buffer.push('\n');
69 }
70
71 for warning in self.warnings.iter().map(|w| w.to_pretty_string()).sorted() {
72 write!(buffer, "//// Warning\n{}", normalise_diagnostic(&warning)).unwrap();
73 buffer.push('\n');
74 buffer.push('\n');
75 }
76
77 buffer
78 }
79}
80
81pub fn to_in_memory_filesystem(path: &Utf8Path) -> InMemoryFileSystem {
82 let fs = InMemoryFileSystem::new();
83
84 let files = walkdir::WalkDir::new(path)
85 .follow_links(true)
86 .into_iter()
87 .filter_map(Result::ok)
88 .filter(|entry| entry.file_type().is_file())
89 .map(|entry| entry.into_path());
90
91 for fullpath in files {
92 let content = std::fs::read(&fullpath).unwrap();
93 let path = fullpath.strip_prefix(path).unwrap();
94 fs.write_bytes(Utf8Path::from_path(path).unwrap(), &content)
95 .unwrap();
96 }
97
98 fs
99}
100
101static FILE_LINE_REGEX: LazyLock<Regex> =
102 LazyLock::new(|| Regex::new(r#"-file\("([^"]+)", (\d+)\)\."#).expect("Invalid regex"));
103
104static FILEPATH_MACRO_REGEX: LazyLock<Regex> =
105 LazyLock::new(|| Regex::new(r#"-define\(FILEPATH, "([^"]+)"\)\."#).expect("Invalid regex"));
106
107pub fn normalise_diagnostic(text: &str) -> String {
108 // There is an extra ^ on Windows in some error messages' code
109 // snippets.
110 // I've not managed to determine why this is yet (it is especially
111 // tricky without a Windows computer) so for now we just squash them
112 // in these cross-platform tests.
113 Regex::new(r"\^+")
114 .expect("^ sequence regex")
115 .replace_all(text, "^")
116 .replace('\\', "/")
117}